[JS] 모듈캐시 이용하여 싱글턴 패턴 구현하기

전세영·2024년 1월 11일

보통 일반적인 클래스사용시
JS에서는 클래스명을 export default로 보내어
new 클래스명(...); 명령어로 인스턴스화해서 사용하는 것이 일반적이다.

하지만 싱글턴패턴을 구현하려면, 어떻게 해야할까?

구현법

구현 방법

  • 클래스 방법 (보편적)
  • 클로저 이용 (JS)
  • 모듈 캐시 이용 (JS)

다른 언어에서도 사용가능한 가장 보편적인 구현법은
클래스선언의 생성자에서 단일 인스턴스를 기억하고있다가
만약 기존생성된 인스턴스가 존재하면
그것을 반환하는식으로 구현한다.

JS에서도 위와같이 클래스 방식을 쓰기도하고,
좀 더 간략한 표현을위해 클로저(Closure)를 이용하여 구현하기도 한다.

하지만 JS의 모듈캐시의 특성을 이용한다면 훨씬 간단명료하게 구현할 수 있다.

// mySingleton.js
class MySingleton {
  constructor() {
    this.value = 0;
  }

  setValue(value) {
    this.value = value;
  }
}

const instance = new MySingleton();
export default instance;
// someFile.js
import mySingleton from "./mySingleton.js";
mySingleton.setValue(5);
console.log(mySingleton.value); // 5

// anotherFile.js
import mySingleton from "./mySingleton.js";
console.log(mySingleton.value); // 5

너무 간단해서 놀라시거나 의아해 하실수도 있지만,
보시는 바와 같이 JS에서는 노드에서실행하든, Browser에서실행하든
인스턴스화 한 것을 리턴해주고 import하기만하면 싱글턴 인스턴스가 보장된다.

someFile에서 someMethod()를 통해 바꾼 객체의 상태값이 유지된채로
anotherFile에서 import할때 그 상태 그대로 넘어온다.

왜 싱글턴이 보장되나? 왜 이렇게 동작하나?

이유 : JS실행환경(노드, 브라우저)에서 제공하는 모듈시스템의 모듈 캐시 기능이 작동하기 때문이다.

JS에서는 모듈시스템(CJS, ESM)을 이용하여 다른 파일의 코드를 가져서 쓰게 되는데(import, require)
이 모듈 시스템을 이용하여 가져오는 값은
'모듈 캐시' 기능을 통해 캐싱이 되며,
2번째 이후의 호출에서는 그 코드를 실행시키지않고 캐싱된 데이터를사용한다.

따라서,
여러번 호출(import, require)을 하더라도
한 페이지를 로딩하는 데 있어서 딱 한번만 실행되는 것이 보장된다.

즉 mySingleton.js 코드는 someFile.js와 anotherFile.js중 먼저실행되는 코드부분에서만 단 한번실행되고,
객체의 상태값까지포함해서 저장/유지 하고 있다가
재차 import시 동일한 해당 객체에 접근할수있게끔해준다.

이 모듈캐싱 역할은 누가 담당/책임지는가?

  • 실행환경 (브라우저, Node.js)이 책임 진다.

JS는
1. node환경 이나,
2. 브라우저환경(ECMA Script Modules)에서

실행되게되는데,

각 환경에서의 모듈시스템은 아래와 같다.
1. Node.js : CommonJS(CJS), ECMA Script Modules(ESM)
2. 브라우저 : ECMA Script Modules

CJS와 ESM 모두 모듈 캐시를 사용하는 것이 기본동작이기때문에
두 실행환경 모두에서 '모듈 캐시'가 보장된다.

모듈캐시가 동작하지 않을 때도 있나?

모듈 캐시가 작동하는것이 일반적인/정상적인 환경이고,
의식적으로 비정상적인환경을 만들려고 상당히 노력을 한 게 아닌 정상적인환경에서는 항상 모듈캐시가 작동한다.

새로고침을 할 경우에도 싱글턴 인스턴스가 유지되나?

  • 따로저장을하지않았기때문에, 새로고침을 할 경우 싱글턴 인스턴스는 유지되지 않는다.

이것을 유지하려면?

  • 아래와 같은 저장소에 저장해야한다.

로컬

  • localStorage
  • sessionStorage
  • cookies
  • indexedDB

서버

  • 서버사이드 스토리지

각 저장소에 대한 간결한 설명

localStorage

  • 브라우저가 종료되어도 유지. 수동으로 브라우저 캐시 클리어시 삭제. 장기간 데이터

sessionStroage

  • 브라우저 탭이 닫힐때까지. 일시적 데이터 ( 세션 등 )

cookies

  • 장기간 가능. 로그인 세션 관리에 주로 사용.

클래스의 인스턴스를 이들에 저장할 수 있나?

localStorage와 sessionStorage

  • JSON 문자열로 직렬화하여 저장가능

쿠키

  • 마찬가지로 JSON 문자열로 직렬화하여 저장가능. 다만 용량제한 : 4KB

IndexedDB

  • 직접 저장가능!. 복잡한 쿼리나 트랜잭션까지 지원! 대량의 데이터 처리가능!

결론

싱글턴 패턴에서 시작하여, 모듈시스템, 모듈캐시, 스토리지에 대해 알아보았다.
스토리지중에서는 대용량 인스턴스를 담기에는 IndexedDB가 상당히 좋았다.
저용량 간소한 데이터를 담기에는 웹스토리지(localStorage, sessionStorage)에 직렬화하여 사용하는 것이 좋다는 것을 알아보았다.

다음에는 IndexedDB의 구현에 대해 간단히 알아보면 더 좋을 거 같다.

이 방식에 대한 다른 사람들의 의견

https://gist.github.com/dmnsgn/4a6ad76de1b5928f13f68f406c70bb09

  • caseinsensitive한 환경에서는 abc.js와 Abc.js를 같은파일로 인식하기때문에 require('abc.js'), require('Abc.js')는 같은파일을 참조하지만 CJS 모듈캐싱은 다른 캐싱파일로 인식하기때문에 캐싱이 동작하지않는다는 주의를 주는 사람(jasonkarns)이 있다.
  • ECMA상에 (파일명, 객체) 식으로 명세되어있었던거같은데 이 내용을 말하는 것 같다.
  • 이사람의 의견에대한 더 자세한내용과 근거는 아래 링크에서 찾을 수 있다.https://stackoverflow.com/questions/67270619/doesnt-es-module-system-guarantee-module-singleton

https://dmnsgn.medium.com/singleton-pattern-in-es6-d2d021d150ae

profile
가치를 빠르고 안전하게 전달하는 개발을 하고 싶습니다.

0개의 댓글