1. WeakMap 오브젝트 개요, new WeakMap()
WeakMap 오브젝트
•
Map과 비교해 구조는 같지만 제약조건이 있는 오브젝트
•
String, Number, Symbol도 key로 사용이 가능한 Map과 다르게 object만 key로 사용가능합니다.
◦
value는 제한 없습니다.
그렇다면 어째서 더 제약이 심한 WeakMap 오브젝트가 있는것일까요? 이는 Map의 문제를 살펴보면서 분석하면 좋습니다. Map에서 key를 object로 삭제한 [key, value] 쌍이 있다고 가정합니다.
여기서 이 key로 설정한 변수가 참조하는 메모리주소를 바꾼다면 object는 삭제되어 참조할수 없지만 Map에서는 object가 남아있습니다. 이럴 경우 메모리 릭(Memory Leak)이 발생합니다.
let sports = {like: "축구"};
const obj = new Map([[sports, "like: 축구"]]);
sports = {like: "농구"};
console.log(obj.get(sports));
console.log(obj.keys().next().value);
JavaScript
복사
[실행 결과]
undefined
{like: "축구"}
1.
sports = {like: "농구"};
⇒ sports 변수가 바라보는 대상 오브젝트가 변경되며 참조 주소값이 바뀝니다.
2.
obj.get(sports);
⇒ 변경된 sports의 메모리주소값은 obj 인스턴스 내에서 key로 쓰이지 않기때문에 undefined반환
3.
obj.keys().next().value
⇒ obj 인스턴스내에 등록된 [key, value]에서 key는 sports가 변경되기 전에 등록한 {like: "축구"}이고 외부에서 해당 오브젝트를 참조하는 변수가 없어졌기 때문에 메모리 릭(memory leak)발생
WeakMap은 이런 문제를 해결하기 위해 나온 오브젝트로 WeakMap의 object를 Garbage Collection(GC)가 같이 지워줍니다.
이 WeakMap 오브젝트 메소드는 Map 오브젝트와 다르게 연결된 메소드가 한정적입니다.
•
set(), get(), has(), delete()
이처럼 CRUD와 관련된 메소드만 있으며 entry의 열거나 이터레이션이 불가능합니다.
new WeakMap()
•
WeakMap인스턴스를 생성하여 반환합니다.
•
파라미터 작성은 기존 Map과 동일하게 대괄호안에 이터러블 오브젝트를 작성합니다.
const empty = new WeakMap();
const sports = {};
const obj = new WeakMap([
[sports, "sports 오브젝트"]
]);
console.log(typeof obj);
JavaScript
복사
[실행 결과]
object
WeakMap 오브젝트 구조
WeakMap 오브젝트 구조
Map 오브젝트 구조
1.
map과 weakmap의 scope를 전개해 구조를 살펴보면 크게 다르지는 않습니다.
2.
Map 오브젝트에는 Symbol(Symbol.species)가 있지만 WeakMap 오브젝트에는 없습니다.
•
constructor 오버라이드가 WeakMap에서는 불가능합니다.
3.
Symbol.iterator도 Map 오브젝트에는 있지만 WeakMap에는 없습니다.
•
이터레이션이 WeakMap에서는 불가능합니다.
4.
그밖에 entries, forEach, keys()등 메소드가 WeakMap에는 없습니다.
const sports = {}
const obj = new WeakMap([[sports, "sports 오브젝트"]]);
JavaScript
복사
WeakMap 인스턴스인 obj의 구조를 전개하면 [[Entries]](엔진에서 설정하는 것)가 있고 내부에 설정된 인덱스 순서대로 [key, value]형태로 설정되어 있습니다.
이는 Map과 인스턴스 구조가 동일합니다.
2. WeakMap오브젝트 메소드: get(), set(), has(), delete()
get()
•
WeakMap 인스턴스에서 key 값이 같은 value를 반환합니다.
•
key값이 존재하지 않을 경우 undefined를 반환합니다.
const fn = () => {};
const obj = new WeakMap([ [fn, "함수"] ]);
console.log(obj.get(fn));
JavaScript
복사
[실행 결과]
함수
set()
•
WeakMap 인스턴스에 key, value 설정
•
첫 번째 파라미터에 key로 사용할 오브젝트를 작성합니다.
◦
primitive value(String, Number, ...)는 사용 불가능합니다.
•
두 번째 파라미터는 값
◦
첫 번째 파라미터의 오브젝트에 대한 값
◦
오브젝트 구분 등의 용도이며 오브젝트에 따라 연동하는 함수 등록
const fn = () => {};
const obj = new WeakMap([ [fn, "함수"] ]);
console.log(obj.get(fn));
obj.set(fn, "함수 변경");
console.log(obj.get(fn));
JavaScript
복사
[실행 결과]
함수
함수 변경
has()
•
WeakMap 인스턴스에서 key의 존재여부를 반환하며 존재할 경우 true 아니면 false를 반환합니다.
const fn = () => {};
const obj = new WeakMap([ [fn, "함수"] ]);
console.log(obj.has(fn));
JavaScript
복사
[실행 결과]
true
delete()
•
WeakMap 인스턴스에서 key와 일치하는 entry를 삭제합니다.
◦
삭제를 성공하면 true 실패하면 false 반환
const fn = () => {};
const fn2 = () => {};
const obj = new WeakMap([ [fn, "함수"] ]);
console.log(obj.delete(fn));
console.log(obj.delete(fn2));
JavaScript
복사
[실행 결과]
true
false
정리
WeakMap의 메소드는 CRUD역할의 get(), set(), has(), delete()만 있다는걸 볼 수 있습니다.
그리고, 4개의 메소드는 모두 key를 사용한다는 공통점이 있습니다. 또한 WeakMap은 iterator, keys, values등등 내부를 순회하며 열거하는 메소드가 없어서 열거가 불가능합니다. 오직 key만 가지고 처리합니다. 그리고 이 key가 object인데 이는 이 오브젝트가 Weak 하기 때문입니다
3. 가비지 컬렉션 처리
가비지 컬렉션(GC)
•
참조했던 object가 바뀌면 참조했던 오브젝트가 가비지 컬렉션 처리됩니다.
코드 분석
let obj = new WeamMap();
let sports = () => {point: 1};
obj.set(sports, "변경전");
sports = () =>{point: 2};
obj.set(sports, "변경후");
JavaScript
복사
1.
let sports = () => {point: 1};
obj.set(sports, "변경전");
⇒ sports에 Function 오브젝트를 할당하고 이것을 WeakMap 인스턴스에 key로 설정합니다.
2.
sports = () =>{point: 2};
⇒ 새로운 함수({point:2})를 생성후 sports에 할당합니다.
⇒바로 위에 sports가 참조하는 메모리주소가 변경됩니다.
⇒ sports가 참조하는 메모리 주소가 바뀌면 앞에 sports에서 참조했던 오브젝트를 호출할 수 없게 됩니다.
⇒ 이렇게 사용할 수 없게 된 {point: 1} 오브젝트는 GC 대상이 됩니다.
⇒ 엔진이 주기적으로 GC 처리를 합니다.
3.
obj.set(sports, "변경후");
⇒ sports를 key로 하여 WeakMap에 설정합니다.
⇒ 앞에서 sports를 key로 설정했기에 여기서 sports를 key로 하여 설정하면 값이 대체되야하지만 두 sports가 참조하는 메모리주소가 각각 다르기 때문에 sports가 추가됩니다.
구조 분석
obj.set(sports, "변경전") 시점
obj.set(sports, "변경후") 시점
1.
sports가 {point:2}로 새로운 오브젝트를 할당해서 sports가 참조하는 오브젝트가 바뀝니다.
2.
변경 후 obj의 [[Entries]]를 펼쳐보면 0과 1 인덱스에 두개의 형태가 등록되어있습니다.
3.
동일한 sports 변수가 참조하는 메모리 주소가 변경되기에 WeakMap에는 각각 저장됩니다.
4.
그렇기에 sports로 저장하는게 아닌 인덱스를 부여해 저장하는 것입니다.
5.
엔진내부에서는 인덱스가 key이며 sports는 프로퍼티 value에서 프로퍼티 키가 됩니다.
4. Map과 WeakMap 차이
지금까지 계속 얘기한 부분들이 차이점이기도 합니다.
•
참조하는 object를 삭제하면 Map은 그대로 가지고 있지만 WeakMap은 GC처리로 삭제됩니다.
let mapObj = new Map();
(function(){
const obj = {key: "value"};
mapObj.set(obj, "Map");
}());//---(1)
let weakObj = new WeakMap();
(function(){
const obj = {key: "value"};
weakObj.set(obj, "WeakMap");
}());//---(2)
JavaScript
복사
즉시 실행함수는 일회용이기에 변수를 저장하지 않을 때 사용합니다. 그렇기에 함수가 끝나면 obj변수는 GC가 메모리에서 지웁니다.
(1): Map은 obj 변수가 함수 종료후 GC에 의해 지워지더라도 Map에 설정된 obj를 지우지 않고 유지합니다.
(2): WeakMap은 앞의 (1)과 동일하지만 Map이 아닌 WeakMap에 저장합니다. WeakMap은 obj변수가 삭제되면 WeakMap에 설정된 obj를 삭제합니다.
구조 분석
let mapObj = new Map();
(function(){
const obj = {key: "value"};
mapObj.set(obj, "Map");
}());
let weakObj = new WeakMap();
(function(){
const obj = {key: "value"};
weakObj.set(obj, "WeakMap");
}());
setTimeout(function(){
debugger
console.log(mapObj);
console.log(weakObj);
}, 3000);
debugger
JavaScript
복사
1.
setTimeout(function(){...});
⇒ GC가 수거대상을 수거하기 위해 즉시 실행되는게 아니기 때문에 setTimeout()으로 3초뒤 출력하도록 setTimeout()함수를 사용합니다.
2.
전개구조를 보면 mapObj 인스턴스에는 key로 {key:"value"}가 등록되있습니다. 실제로 GC에 의해 obj는 수거되었지만 말이죠. 이는 메모리 릭(memory leak)을 유발하여 크리티컬할 수 있습니다.
3.
weakObj의 [[Entries]]를 전개해보면 No properties입니다. GC에서 obj({key: "value})를 수거할 때 weakMap의 해당 key 도 지우기 때문입니다.
정리
Map은 기본적인 CRUD 메소드 뿐아니라 forEach, keys, values등등 반복순회하는 이터레이션 메소드들 또한 연결되어있습니다. 하지만, WeakMap은 CRUD 메소드만 연결되어있고 Key를 통해서만 접근할 수 있으며 열거할수 없습니다.
이런 메소드의 제한은 WeakMap의 특징인데, Map의 경우 GC에 의해 key가 수거되거나 하지 않습니다. 그렇기 때문에 그렇기 때문에 엔트리 사이즈가 정적이고 명시적으로만 변경할 수 있습니다.
반면, WeakMap은 GC에 의해 엔트리가 유동적으로 변경될 수 있기 때문에 열거나 나열할수 없습니다.