자바스크립트 톺아보기5

이지영·2025년 1월 3일

iterable 객체

반복 가능한 객체는 배열을 일반화한 객체이다.

이터러블 이라는 개념을 사용하면 어떤 객체에든 for..of 반복문을 적용할 수 있다.

배열을 대표적인 이터러블이다.
배열 외에도 다수의 내장 객체가 반복 가능하다. 문자열 역시 이터러블의 예이다.

배열이 아닌 객체가 있는데, 이 객체가 어떤 것들의 컬렉션(목록, 집합 등)을 나타내고 있는 경우, for..of 문법을 적용할 수만 있다면 컬렉션을 순회하는데 유용할 것이다.

Symobol.iterator

직접 이터러블 객체를 만들어 이터러블이라는 개념을 이해해보자
for..of를 적용하기에 적합해 보이는 배열이 아닌 객체를 만들어보자.
예시의 객체 range는 숫자 간격을 나타내고 있다.

let range = {
  from: 1,
  to: 5
}
// 아래와 같이 for..of가 동작할 수 있도록 하는게 목표이다.
// for(let num of range) ... num = 1,2,3,4,5

range를 이터러블로 만들려면(for..of)가 동작하도록 하려면 객체에 Symbol.iterator(특수 내장 심볼)라는 메서드를 추가해야한다.

Symbol.iterator?

자바스크립트에서 이터러블 객체를 정의하는 데 사용되는 심볼이다.

이터러벌 객체는 for..of루프와 같은 구문에서 반복할 수 있는 객체를 의미한다.

Symbol.iterator는 이러한 객체가 어떻게 반복되는지를 정의하는 메서드를 제공한다.

이터러블 객체는 Symbol.iterator 메서드를 포함해야 하며, 이 메서드는 이터레이터 객체를 반환해야한다.

이터레이터 객체는 next() 메서드를 가지고 있으며, 이 메서드는 다음 값을 반환하는 역할을 한다

반환 값은 value와 done 속성을 가진 객치이다.

  • value: 현재 이터레이션의 값
  • done: 반복이 완료되었는지를 나타내는 불리언 값
const myArray = [1,2,3];
const iterator = myArray[Symbol.iterator]();
console.log(iterator.next()); // {value: 1, done: false}
console.log(iterator.next()); // {value: 2, done: false]
console.log(iterator.next()); // {value: 3, done: flase}
console.log(iterator.next()); // {value: undefined, done: true}

사용자 정의 이터러블 객체

사용자가 직접 이터러블 객체를 만들 수도 있다.

const myIterable = {
  *[Symbol.iterator] () {
    yield 1;
    yield 2;
    yield 3;
  }
}

for (const value of myIterable) {
  console.log(value); // 1,2,3
}

위의 예제에서 *를 사용하여 제너레이터 함수를 정의하고, 이를 통해 이터러블 객체를 만들었다.
이터레이터는 yield를 사용하여 값을 순차적으로 반환한다.

제너레이터 함수란?

function* 키워드를 사용하여 정의되는 특별한 종류의 함수이다.

제너레이터 함수는 호출될 때 실행을 중단하고, yield키워드를 사용하여 값을 반환할 수 있다.

이 함수는 호출될 때마다 이전 상태를 기억하고, 다시 호출되면 중단된 지점부터 실행을 재개할 수 있다.

제너레이터 함수의 특징
1. 생성기 객체: 제너레이터 함수를 호출하면 생성기(generator)객체가 반환된다. 이 객체는 이터레이터의 인터페에스를 구현하고 있다.
2. yield키워드: yield키워드를 사용하여 함수의 실행을 중단하고 값을 반환할 수 있다. 이후 다시 호출되면 yield다음 코드부터 실행된다.
3. next()메서드: 생성기 객체는 next() 메서드를 가지고 있어 이 메서드를 호출할 때마다 다음 yield표현식까지 실행되고, 해당 값을 반환한다.
4. 완료 상태: 모든 yield가 실행된 후에는 next() 메서드를 호출하면 {value: undefined, done: true}형태의 객체를 반환하여 함수가 종료되었음을 나타낸다.

제너레이터 함수는 이러한 특성 덕분에 복잡한 비동기 작업이나 데이터 흐름을 관리하는 데 매우 유용하다.

range를 반복 가능한 객체로 만들기

  1. for..of최초 호출 시 Symbol.iterator가 호출된다.
    Symbol.iterator는 이터레이터 객체를 반환한다.
  2. 이후 for..of는 반환된 이터레이터 객체만을 대상으로 동작하는데, 이때 다음 값도 정해진다.
  3. for..of반복문에 의해 반복마다 next()가 호출된다.
  4. next()는 값을 객체 {done:..., value:...}형태로 반환해야한다.
let range = {
  from : 1,
  to: 5
};

range[Symbol.iterator] = function () {

  return {
    current: this.from;
    last: this.to,

    next() {
      if(this.current <= this.last) {
        return {done false, value: current++};
      } else {
        return {done: true}
      }
    }
  }
}
// 이제 의도한 대로 동작한다.
for(let num of range) {
  alert(num); // 1, 2,3,4,5
}

range자체를 이터레이터로 만들면 코드는 좀 더 간단해진다.

let ragne = {
   from: 1,
   to: 5,

   [Symbol.iterator]() {
      this.current = this.from
      return this;
   },
   next() {
       if(this.current < = this.to) {
         return {done: fale, value: this.current++};
       } else {
         return {done: true};
       }
   }
}
for (let num of range) {
  alert(num); 1,2,3,4,5
}

이제 rangeSymbol.iterator가 객채 range자체를 반환한다.
반환된 객체엔 필수 메서드인 next()가 있고 this.current에 반복이 얼마나 진행되었는지를 나타내는 값도 저장되어있다.
코드는 좀 더 짧아졌다.
단점은 두 개의 for..of반복문을 하나의 객체에 동시에 사용할 수 없다는 점이다.
이터레이터가 하나뿐이여서 두 반복분이 반복 상태를 공유하기 떄문이다.
그런데 두 개의 for..of를 사용하는 것은 비동기 처리에서도 흔한 케이스가 아니다.
이유는 이미 this.current 는 이미 5를 초과했기 때문에 더 이상 반환할 값이 없으므로, 아무것도 출력되지 않는다.

무한개의 이터레이터

무수히 많은 이터레이터도 가능하다.
range에서 range.to에 Infinity를 할당하면 range가 무한대로 된다. 무수히 많은 의사 난수를 생성하는 이터러블 객체를 만드는 것도 가능하다. 이 방법이 유용하게 쓰이는 경우도 있다.
next에는 제약사양이 없다. next가 값을 계속 반환하는 것은 정상적인 동작이다.
물론 위와 같은 이터러블에 for..of반복문을 사용하면 끝이 없을거다. 하지만 break를 사용하면 언제든 반복을 멈출 수 있다.

문자열은 이터러블이다.

배열과 문자열은 가장 광범위하게 쓰이는 내장 이터러블이다.
for..of는 문자열의각 글자를 순회한다.

for(let char of "test") {
  // 글자 하나당 한 번 실행된다.
  alert(hcar); // t,e,s,t
}

서로게이트 쌍(surrogate pair)에도 잘 동작한다.

이터레이터를 명시적으로 호출하기

for..of를 사용했을 때와 동일한 방법으로 문자열을 순회할 건데, 이번엔 직접 호출을 통해서 순회해보자

다음 코드는 문자열 이터레이터르 만들고 여기서 값을 수동으로 가져온다.

let str = "Hello";
let iterator = str[Symbol.iterator]();
while (true) {
  let resut = iterator.next();
  if(result.done) break;
  alert(resut.value); // 글자가 하나씩 출력된다.
}

이터레이터를 명시적으로 호출하는 경우는 거의 없는데, 이 방법을 사용하면 for..of를 사용하는 것보다 반복 과정을 좀 더 잘 통제할 수 있다는 장점이 있다.
반복을 시작했다가 잠시 멈춰 다른 작업을 하다가 다시 반복을 시작하는 것과 같이 반복 과정을 여러 개로 쪼개는 것이 가능하다.

이터러블과 유사배열

비슷해보이지만 아주 다른 용어 두 가지가 있다.

  • 이터러블(iterable)은 위에서 설명한 바와 같이 메서드 Symbol.iterator가 구현된 객체이다.
  • 유사배열은 인덱스와 length프로퍼티가 있어서 배열처럼 보이는 객체이다.
  • 이터러블과 유사 배열은 대개 배열이 아니기 때문에 push, pop등의 메서드를 지원하지 않는다.

이터러블과 유사 배열을 배열처럼 다루고 싶을 때 이런 특징은 불편함을 초래한다.
range에 배열 메서드를 사용하여 무언가를 하고 싶을 때 처럼 말이다.
어떻게 하면 이터러블과 유사 배열에 배열 메서드를 적용할 수 있을까?

Array.from

범용 메서드 Array.from은 이터러블이나 유사 배열을 받아 진짜 Array를 만들어준다.
이 과정을 거치면 이터러블이나 유사 배열에 배열 메서드를 사용할 수 있다.

let arrayLike = {
  0: "Hello",
  1: "World",
  length: 2
}
let arr = Array.from(arrayLike);
alert(arr.pop()); // World pop 메서드가 제대로 동작한다

Array.from은 객체를 받아 이터러블이나 유사 배열인지 조사한다.
넘겨 받은 인수가 이터러블이나 유사 배열인 경우 새로운 배열을 만들고 객체의 모든 요소를 새롭게 만든 배열로 복사한다.

Arr.from은 str.split고과 달리 문자열 자체가 가진 이터러블 속성을 이용해 동작한다.
따라서 for..of처럼 서로게이트 쌍에도 제대로 적용된다.

Summary

for..of을 사용할 수 있는 객체를 이터러블이라고 부른다.

  • 이터러블엔 메서드 Symbol.iterator가 반드시 구현되어 있어야한다.
    • obj[Symbol.iterator]의 결과는 이터레이터라고 부른다. 이터레이터는 이어지는 반복 과정을 처리한다.
    • 이터레이터엔 객체 {done: Boolean, vlaue: any}을 반환하는 메서드 next()가 반드시 구현되어 있어야한다. 여기서 done:true는 반복이 끝났음을 의미하고 그렇지 않은 경우엔 value 가 다음 값이 된다.
  • 메서드 Symbol.iterator는 for..of에 의해 자동으로 호출되는데, 개발자가 명시적으로 호출하는 것도 가능하다.
  • 문자열이나 배열 같은 애장 이터러벌에도 Symbol.iterator가 구현되어 있다.
  • 문자열 이터레이터는 서로게이트 쌍을 지원한다.

인득스와 length프로퍼티가 있는 객체는 유사배열이라 불린다. 유사 배열 객체엔 다양한 프로퍼티와 메서드가 있을 수 있는데 배열 내장 메서드는 없다.

명세서를 보면 대부분의 메서드는'진짜'배열이 아닌 이터러블이나 유사 배열을 대상으로 쓰여 있는걸 볼 수 있다.
Array.from(obj[, mapFn, thisArg]) 을 사용하면 이터러블이나 유사 배열인 obj를 진짜 Array로 만들 수 있다.
이렇게 하면 obj에도 배열 메서드를 사용할 수 있다.
선택인수 mapFn와 thisArg는 각 요소에 함수를 적용할 수 있게 해준다.

맵과 셋

  • 객체: 키가 있는 컬렉션을 저장
  • 배열: 순서가 있는 컬렉션을 저장
    지금까지는 위와 같은 복잡한 자료구조를 학습했다.
    하지만 현실 세계를 반영하기에는 이 두 자료구조만으론 부족해서 Map과 Set이 등장하게 되었다.

Map

맵은 키가 있는 데이터를 저장한다는 점에서 객체와 유사하다. 다만 맵은 키에 다양한 자료형을 허용한다는 점에서 차이가 있다.


맵에는 다음과 같은 주요 메서드와 프로퍼티가 있다.

  • new Map() : 맵을 만든다.
  • map.set(key, value) : key를 이용해 value 를 저장한다.
  • map.get(key) : 키에 해당하는 값ㅇ르 반환한다. key가 존재하지 않으면 undefined를 반환한다.
  • map.hs(key): key가 존재하면 true, 존재하지 않으면 false를 반환
  • map.delete(key) : key에 해당하는 값을 삭제
  • map.clear() : 맵 안의 모든 요소를 제거
  • map.size : 요소의 개수를 반환

맵은 객체와 달리 키를 문자형으로 변환하지 않는다. 키엔 자료형 제약이 없다.


map[key]=2로 값을 설정할 수 있다 하지만 이 방식은 map을 일반 객체처럼 취급하게 된다. 따라서 여러 제약이 생긴다


map을 사용할 땐 map전용 메서드 set,get등을 사용해야만 한다.


맵은 키로 객체를 허용한다
객체를 키로 사용할 수 있다는 점은 맵의 가장 중요한 기능 중 하나이다.
객체에는 문자열 키를 사용할 수 있다 하지만 객체 키는 사용할 수 없다.

체이닝

map.set을 호출할 떄마다 맵 자신이 반환된다

이를 이용하면 map.set을 체이닝 할 수 있다.

map.set('1', 'str1')
  .set(1, 'num1')
  .set(true, 'bool1');

맵의 요소에 반복 작업하기

다음 세 가지 메서드를 사용해 맵의 각 요소에 반복 작업을 할 수 있다.

  • map.keys() : 각 요소의 키를 모은 반복 가능한 이터러블 객체를 반환
  • map.values() : 각 요소의 값을 모은 이터러블 객체를 반환
  • map.entries() : 요소의 [키,값]을 한 쌍으로 하는 이터러블 객체를 반환한다.

맵은 삽입 순서를 기억한다.

맵은 값이 삽입된 순서대로 순회를 실시한다.
객체가 프로퍼티 순서를 기억하지 못하는 것과는 다르다.

또한 맵은 배열과 유사하게 내장 메서드 forEach도 지원한다.

또한 Object.entries를 사용하여 객체를 맵으로 바꿀 수 있고

Object.fromEntries를 사용하여 맵을 객체로 바꿀 수 있다.

Set

셋은 중복을 허용하지 않는 값을 모아놓은 특별한 컬렉션이다.
셋에 키가 없는 값이 저장된다.

주요메서드

  • new Set(iterable) : 셋을 만든다. 이터러블 객체를 전달받으면(대개 배열 전달받음) 그 안의값을 복사해 셋에 넣어준다.
  • set.add(value) : 값을 추가하고 셋 자신을 반환한다.
  • set.delte(value) : 값을 제거하고 호출 시점에 셋 내에 제거할 값이 있어서 성공하면 true아니면 false를 반환한다.
  • set.has(value) : 셋 내에 값이 존재하면 true, 아니면 false를 반환
  • set.clear() : 셋을 비운다.
  • set.size : 셋에 몇 개의 값이 있는지 세준다.

셋 내에 동일한 값이 있다면 et.add(value)을 아무리 많이 호출하더라도 아무런 반응이 없을것이다.
셋 내의 값에 중복이 없는 이유가 바로 이 떄문이다.
방문자 방명록을 만든다고 가정햇을 때 한 방뭉자가 여러 번 방문해도 방문자를 중복해서 기록하지 않을 때 즉, 한 방문자는 단 한번만 기록 되어야할 때 적합한 자료구조가 셋이다.

let set = new Set();
let john = {name: "John"};
let pete = {name: "Pete"];
let mary = {name: "Mary"};

set.add(john);
set.add(pete);
set.add(mary);
set.add(john);
set.add(mary);

alert(set.size); // 3
for (let user of set) {
  alert(user.name); // John, Pete, Mary순으로 출력됨
}

셋 대신 배열을 사용하여 중복 값 여부는 배열 메서드인 arr.find를 이용해 확인할 수 있다.
하지만 arr.find는 배열 내 요소 전체를 뒤져 중복 값을 찾기 때문에, 셋보다 성능 면에서 떨어진다.
반면 셋은 값의 유일무이함을 확인하는데 최적화되어있다.

셋의 값에 반복 작업하기

for..of나 forEach를 사용하면 셋의 값을 대상으로 반복 작업을 수행할 수 있다.

let set = new Set(["oranges", "apples", "bananas"]);
for(let value of set) alert(value);
// forEach를 사용해도 동일하게 동작
set.forEach((value, valueAgain, set) => {
  alert(value);
})

forEach에 쓰인 콜백함수는 세 개의 인수를 받는데, 첫 번째는 값, 두 번쨰도 같은 값인 valueAgain을 받고있다.

세 번째는 목표하는 객체(셋)이다. 동일한 값이 인수에 두 번 등장하고 있다.

이렇게 구현된 이유는 맵과의 호환성 때문이다
맵의 forEach에 쓰인 콜백이 세 개의 인수를 받을 떄를 위해서이다.

이렇게 구현해 놓았기 때문에 맵을 셋으로 혹은 셋을 맵으로 교체하기 쉽다.

셋에도 맵과 마찬가지로 반복 작업을 위한 메서드가 있다.

  • set.keys() : 셋 내의 모든 값을 포함하는 이터러블 객체를 반환
  • set.values() : set.keys와 동일한 작업을 한다. 맵 과의 호환성을 위해 만들어진 메서드이다
  • set.entries() : 셋 내의 각 값을 이용해 만든 [vlaue, value]배열을 포함하는 이터러블 객체를 반환한다. 맵과의 호환성을 위해 만들어졌다.

과제

배열에서 중복 요소 제거하기

function unique(arr) {
  retrun Array.from(new Set(arr));
}

return Array.from(new Set(arr));

에너그램 걸러내기

오답 : 이유는 원래의 단어를 잃어버린다.

  function aclean(arr) {
    let newArray = arr.map((item)=>item.toLowerCase().split('').sort().join(''));
    return Array.from(new Set(newArray);
  }

정답
정렬된 문자를 키값으로 해서 중복 저장되지 않게 저장한 후 key(정렬된 단어)가 아닌 values(원래의 단어 )를 리턴해준다.

function aclean(arr) {
  for (let word of arr ) {
    let sorted = word.toLowerCase().splite('').sort().join('');
    map.set(srted,word);
  }
  return Array.from(map.values());
}

반복 가능한 객체의 키

map.keys()를 사용해 배열을 반환받고, 이 배열을 변수에 저장해 .push와 같은 배열 메서드를 적용하고 싶다고 해봅시다.
작동하지 않네요
이유가 무엇일까요? keys.push가 작동하게 하려면 어떻게 코드를 고쳐야할까요?

let map = new Map();
map.set("name","John");
let keys = map.keys();
// Error: keys.push is not a function
keys.push("more");

동작하지 않는 이유는 map은 배열이 아니다 따라서 배열 메서드가 적용되지 않는다.
따라서 map을 배열로 바꾼 후 push를 하던가, map.set을 사용해야한다.

하지만 문제에 주어진 value값이 없으니 map.set이 아닌
map을 배열로 바꾼 후 push가 동작하는 방법을 사용해보자

정답:

let keys = Array.from(map.keys());
keys.push("more"); 

위크맵과 위크셋

가비지 컬렉션에서 배웠듯이 자바스크립트 엔진은 도달 가능한(그리고 추후 사용될 가능성이 있는)값을 메모리에 유지한다.

let john = {name: "John"};
// 위 객체는 john이라는 참조를 통해 접근할 수 있다.
// 그런데 참조를 null로 덮어쓰면 위 객체에 더 이상 도달이 가능하지 않게 되어
john = null;
// 객체가 메모리에서 삭제된다.

자료구조를 구성하는 요소도 자신이 속한 자료구조가 메로리에 남아있는 동안 대게 도달 가능한 값으로 취급되어 메모리에서 삭제되지 않는다.
객체의 프로퍼티나 배열의 요소, 맵이나 셋을 구성하는 요소들이 이에 해당한다.

예를들어 배열에 객체 하나를 추가해 보자 <br>
이때 배열이 메모리에 남아있는 한 배열의 요소인 이 객체도 메모리에 남아있게 된다.
이 객체를 참조하는 것이 아무것도 없더라도 메모리에 남아있게 된다.
```jsx
let john = {name: "John"};
let array = [ john ];
john = null // 참조를 null로 덮어씀
// john을 나타내는 객체는 배열의 요소이기 떄문에 가비지 컬렉터의 대상이 되지 않는다.
// array[0]을 이용하면 해당 객체를 얻는 것도 가능하다.
alert(JSON.stringify(array[0]));

맵에서 객체를 키로 사용한 경우 역시 맵이 메모리에 있는 한 객체도 메모리에 남는다.
가비지 컬렉터의 대상이 되지 않는다.

let john = { name: "John" };
let map = new Map();
map.set(john, "...");

john = null // 참조를 null로 덮어씀

// john을 나타내는 객체는 맵 안에 저장되어있다.
// map.keys()를 이용하면 해당 객체를 얻는 것도 가능하다.
for(let obj of map.keys()) {
  alert(JSON.stringify(obj));
}
alert(map.size);

이러한 관점에서 위크맵은 일반 맵과 전혀 다른 양상을 보인다.
위크맵을 사용하면 키로 쓰인 객체가 가비지 컬렉션의 대상이 된다.

위크맵

맵과 위크맵의 첫 번째 차이는 위크맵의 키가 반드시 객체여야한다는 점이다.
원시값은 위크맵의 키가 될 수 없다.

let weakMap = new WakMap();
let obj = {};
weakMap.set(obj,"ok"); // 정상적으로 동작한다 (객체 키)
// 하지만 문자열은 키로 사용될 수 없다.
weakMap.set("test", "Whoops"); // Error: Invalid value used as wak map key

위크맵의 키로 사용된 객체를 참조하는 것이 아무것도 없다면 해당 객체는 메모리와 위크맵에서 자동으로 삭제된다

let john = {name: "John"};
let weakMap = new WeakMap();
weakMap.set(john, "...");
john = null; // 참조를 덮어씀
// john을 나타내는 객체는 이제 메모리에서 지워진다

john을 나타내는 객체는 오르지 위크맵의 키로만 사용되고 있으므로 참조를 덮어쓰게 되면 이 객체는 위크맵과 메모리에서 자동으로 삭제된다.

맵과 위크맵의 두 번째 차이는 위크맵은 반복작업과 keys(), values(), entries() 메서드를 지원하지 않는다는 점이다.
따라서 위크맵에선 키나 값 전체를 얻는게 불가능하다.

위크맵이 지원하는 메서드

  • weakMap.get(key)
  • weakMap.set(key,value)
  • weakMap.delte(key)
  • weakMap.has(key)

왜 이렇게 적은 메소드만 제공할까? 원인은 가비지 컬렉션의 동작 방식떄문이다.
위 예시의 john을 나타내는 객체처럼 객체는 모든 참조를 잃게 되면 자동으로 가비지 컬렉션의대상이 된다.
그런데 가비지 컬렉션의 동작 시점은 정확히 알 수 없다.
가비지 컬렉션이 일어나는 시점은 자바스크립트 엔진이 결정한다.
객체는 모든 참조를 잃었을 때, 그 즉시 메모리에서 삭제될 수도 잇고, 다른 삭제 작업이 있을 때까지 대기하다가 함께 삭제될 수도 있다.
현재 위크맵에 요소가 몇 개 있는지 정확히 파악하는 것 자체가 불가능한것이다.
가비지 컬렉터가 한 번에 메모리를 청소할 수도 있고, 부분부분 메모리를 청소할 수도 있으므로 위크맵의 요소(키/값) 전체를 대상으로 무언가를 하는 메서드는 동작 자체가 불가능하다.

그럼 어떤 경우에 위크맵을 사용할까?

유스 케이스: 추가 데이터

위크맵은 부차적인 데이터를 저장할 곳이 필요할 떄 그 진가를 발휘한다.
서드파티 라이브러리아 같은 외부 코드에 속한 객체를 가지고 작업을 해야한다고 가정해보자
이 객체에 데이터를 추가해줘야하는데, 추가해 줄 데이터는 객체가 살아있는 동안에만 유효한 상황이다.

이럴 떄 위크맵을 사용할 수 있다.

위크맵에 원하는 데이터를 저장하고 이떄 키는 객체를 사용하면된다. 이렇게 하면 객체가 가비지 컬렉션의 대상이 될 때, 이 데이터도 함께 사라지게 된다.

weakMap.set(john, "비밀문서");
// john이 사망하면 비밀문서는 자동으로 파기된다

좀 더 구체적인 예시를 알아보자

아래에 사용자의 방문 횟수를 세어주는 코드가 있다.
관련 정보는 맵에 저장하고있는데, 맵 요소의 키엔 특정 사용자를 나타내는 객체를, 값엔 해당 사용자의 방문 횟수를 저장하고 있다

어떤 사용자의 정보를 저장할 필요가 없어지면 해당 사용자의 방문 횟수도 저장할 필요가 없어진다.

아래 함수는 맵을 사용해 방문 횟수를 세준다.

// visitsCount.js
let vlisitsCountMap = new Map(); // 맵에 사용자의 방문 횟수를 저장함.

// 사용자가 방문하면 방문 횟수를 늘려준다.
function countUser(user) {
  let count = visitsCountMap.get(user) || or;
  visitisCountMap.set(user, count+1);
}

아래는 John이라는 사용자가 방문했을 때, 어떻게 방문 횟수가 증가하는지를 보여즌다.

// main.js
let john = {name: "John"};
countUser(john); // John의 방문 횟수를 증가시킨다.

// John의 방문 횟수를 셀 필요가 없어지면 아래와 같이 john을 null로 덮어쓴다.
john = null;

이제 john을 나타내는 객체는 가비지 컬렉션의 대상이 되어야하는데 visitsCountMap 의 키로 사용되고 있어서 메모리에서 삭제되지 않는다.

특정 사용자를 나타내는 메모리가 사라지면 해당 객체에 대한 정보(방문 횟수)도 우리가 손수 지워줘야한다.

이렇게 하지 않으면 visitsCOuntMap가 차지하는 메모리 공간이 한없이 커질 것이다.

애플리케이션 구조가 복잡할 땐, 이렇게 쓸모 없는 데이터를 수동으로 비워주는 게 꽤 골치아프다.

이런 문제는 위크맵을 사용해 예방할 수 있다.

// visitisCount.js
let visitisCountMap = new WeakMap(); // 위크맵에 사용자의 방문 횟수를 저장

// 사용자ㅏㄱ 방문하면 방문 횟수를 늘려준다.
function countUser(user) {
  let count = visitsCountMap.get(user) || 0;
  visitsCountMap.set(user, count + 1 );
}

위크맵을 사용해 사용자 방문 횟수를 저장하면 visitsCountMap을 수동으로 청소해줄 필요가 없다.
john을 나타내는 객체가 도달 가능하지 않은 상태가 되면 자동으로 메모리에서 삭제되기 떄문이다.
위크맵의 키 john에 대응하는 값도 자동으로 가비지 컬렉션의 대상이 된다.

0개의 댓글