자바스크립트 톺아보기6

이지영·2025년 1월 4일

유스케이스 캐싱

위크맵은 캐싱이 필요할 때 유용하다.
캐싱은 시간이 오래 걸리는 작업의 결과를 저장해서 연산 시간과 비용을 절약해주는 기법이다.
동일한 함수를 여러 번 호출해야 할 때, 최초 호출 시 반환된 값을 어딘가에 저장해 놓았다가 그 다음엔 함수를 호출하는 대신 저장된 값을 사용하는 게 캐싱의 실례이다.
아래 예시는 함수 연산 결과를 맵에 저장하고 있다.

// cache.js
let  cahe = new Map();

// 연산을 수행하고 그 결과를 맵에 저장
function process(obj) {
  if(!cache.has(obj){
    let result = /* 연산 수행 */ obj;
    cache.setset(obj, result);
  }
  return cache.get(obj);
}

// 함수 process()를 호출
// main.js
let obj = {};
let result1 = process(obj); // 함수를 호출

// 동일한 함수를 두 번쨰 호출할 땐,
let result2 = process(obj); 연산을 수행할 필요 없이 맵에 저장된 결과를 가져오면 된다.

// 객체가 쓸모없어지면 아래와 같이 null로 덮어쓴다.
obj = nul;

alert(cache.size); // 1 (객체가 여전히 cache에 남아있다. 메모리가 낭비되고 있다.

process(obj)를 여러 번 호출하면 최초 호출할 때만 연산이 수행되고 그 이후엔 연산 결과를 cache에서 가져온다.
그런데 맵을 사용하고 있어서 객체가 필요없어져도 cache를 수동으로 청소해줘야 한다.

맵을 위크맵으로 교체하면 이런 문제를 예방할 수 있다.
객체가 메모리에서 삭제되면 캐시에 저장된 결과(함수 연산 결과) 역시 메모리에서 자동으로 삭제되기 때문이다.

// cache.js
let cache = new WeapMap();

// 연산을 수행하고 그 결과를 위크맵에 저장
function process(obj) {
  if( !cache.hs(obj)) {
    let result = /* 연산 수행 */ obj;
    cache.set(obj, result);
  }
  return cache.get(obj);
}

// main.js
let obj = {}
let result1 = process(obj);
let result2 = process(obj);

// 객체가 필요없어지면 null로 덮어쓰기
obj = null;

// 이 예시에선 맵을 사용한 예시처럼 cache.size를 사용할 수 없다.
// 하지만 obj가 가비지 컬렉션의대상이 되므로, 캐싱된 데이터 역시 메모리에서삭죄될 것이다.
// 삭제가 진행되면 chche엔 그 어떤 요소도 남아있지 않다.

위크넷

  • 위크셋은 셋과 유사한데, 객체만 저장할 수 있다는 점이 다르다. 원시값을 저장할 수 없다.

  • 셋 안의 객체는 도달 가능할 때만 메모리에서 유지된다.

  • 셋과 마찬가지로 위크셋이 지원하는 메서드는 단출하다.add, has, delete를 사용할 수 있고 size, key() 나 반복 작업 관련 메서드는 사용할 수 없다.

    워크맵과 유사하게 위크셋도 부차적인 데이터르 저장할 때 사용할 수 있다.
    다만 위크셋엔 위크맵처럼 복잡한 데이터를 저장하지 않는다.
    대신 예 또는 아니오 와같은 간단한 답변을 얻는 용도로 사용된다.
    물론 위크셋에 저장되는 값들은 객체들이다
    예시와 함께 위크셋의 용도를 알아보자
    사용자의 사이트 방문여부를 추적하는 용도로 위크셋을 사용하고 있다.

lset visitedSet = new WeakSet();

let john = {name: "John"};
let pete = {name: "Pete"};
let mary = {name: "Mary"};

visitiedSet.add(john);
visitiedSet.add(pete);
visitiedSet.sdd(john);

// visitiedSet엔 두 명의 사용자가 저장될 것이다.

// alert(visitiedSet.has(john)); // true
// alert(visitiedSet.has(mary)); // false

john = null;
// visitedSet에서 john을 나타내는 객체가 자동으로 삭제된다.

위크맵과 위크셋의 가장 큰 단점은 반복 작업이 불가능하다는 점이다.
위크맵이나 위크넷에 저장된 자료를 한 번에 얻는게 불가능하다.
이런 단점은 불편함을 초래하는 것 같아 보이지만, 위크맵과 위크셋을 이용해 할 수 있는 주요 작업을 방해하지 않는다.
위크맵과 위크셋은 객체와 함께 추가 데이터를 저장하는 용도로 쓸 수 있다.

Summary

위크맵은 맵과 유사한 컬렉션이다.
위크맵을 구성하는 요소의 키는 오직 객체만 가능하다
키로 사용된 객체가 메모리에서 삭제되면 이에 대응하는 값 역시 삭제된다.

위크셋은 셋과 유사한 컬렉션이다.
위크셋엔 객체만 저장할 수 있다. 위크셋에 저장된 객체가 도달 불가능한 상태가 되면 해당 객체는 메모리에서 삭제된다.

두 자로구조 모두 구성 요소 전체를 대상으로 하는 메서드를 지원하지 않는다.
구성 요소 하나를 대상으로 하는 메서드만 지원한다.

객체엔 주요 자료를, 위크맵과 위크셋엔 부수적인 자료를 저장하는 형태로 위크맵과 위크셋을 활용할 수 있다.
객체가 메모리에서 삭제되면(그리고 오로지 위크맵과 위크셋의 키만 해당 객체를 참조하고 있다면) 위크맵이나 위크셋에 저장된 연관 자료들 역시 메모리에서
자동으로 삭제된다.

과제

읽음 상태인 메시지 저장하기

let messages = [
  { text: "Hello", from: "John" },
  { text: "Howe goes?", from: "john" },
  { text: "See you soon", from: "Alice" }
]

해답

let readMessages = new WeakSet();

// 메시지 두 개가 읽음 상태로 변경됨
readMessages.add(messages[0]);
readMessages.add(message[1]);
// readMessages엔 요소 두 개가 저장된다.

// 첫 번째 메시지를 다시 읽어보자!
readMessages.add(messages[0])
// readMessages에는 요소 두 개가 여전히 저장되어 있다(중복 요소 없음)

// message[0]은 읽음 상태인가요? 에 대한 답변
alert('message 0 은 읽음 상태인가요?' + readMessages.has(message[0])); // true

messages.shift();

읽은 날짜 저장하기

메시지를 언제 읽었나요? 라는 질문에 대한 답이 반환되는 자료구조

let readMessgaes = new WeakMap();
readMessages.set(message[0], new Date(2024,1,4))

alert(readMessage.get(message[0]));

Object.keys, values, entries

개별 자료 구조에서 한발 뒤로 물러나 순회에 대해 알아보자
이전 챕터에서 순회에 필요한 메서드 map.keys(), map.values(), map.entries()에 대해 알아보았다.
이 메서드들은 포괄적인 용도로만 만들어졌기 때문에 메서드를 적용할 자료구조는 일련의 합의를 줒ㄴ수해야한다.
커스텀 자료구조를 대상으로 순회를 해야한다면 이 메서드들을 쓰지 못하고 직접 메서드를 구현해야한다.

keys(), values(), entries()를 사용할 수 있는 자료구조

  • Map
  • Set
  • Array
    일부 객체에도 순회 관련 메서드가 있지만 차이가 있다.

Objkect.keys, values, entries

일반 객체에는 다음과 같은 메서드를 사용할 수 있다.

  • Objkect.keys(obj) : 객체의 키만 담은 배열을 반환
  • Object.values(obj) : 객체의 값만 담은 배열을 반환
  • Object.entries(obj) : [키, 값] 쌍을 담은 배열을 반환

Map, Set, Array 전용 메서드와 일반 객체용 메서드의 차이를 (맵을 기준으로) 비교하면 다음과 같다.

맵: map.keys()
객체: Object.keys(obj)

맵 반환값: 이터러블 객체
객체 반환값: 진짜 배열

이렇게 문법이 다른 이유는 유연성 떄문이다.
자바스크립트에서는 복잡한 자료구조 전체가 객체에 기반한다.
그러다보니 객체 data에 자체적으로 data.values()라는 메서드를 구현해 사용하는 경우가 있을 수 있다.
이렇게 커스텀 메서드를 구현한 상태라도 Object.values(data)같은 형태로 메서드를 호출할 수 있으면
커스텀 메서드와 내장 메서드 둘 다 사용할 수 있다.

두 번째 차이는 메서드 Object.*를 호출하려면 iterable객체가 아닌 객체의 한 종류인 배열을 반환한다는 점이다.

let user = {
  name: "john",
  age: 30
};
  • Object.keys(user) = ["name","age"]
  • Object.values(user) = ["john",30];
  • Object.entries(user) = [["name","John"], ["age",30] ]

아래 예시처럼 Object.values를 사용하면 프로퍼티 갑을 대상으로 원하는 작업을 할 수 있다.

let user = {
  name: "Violet",
  age: 30
}

for( let value of user ) {
  alert(value); // violet과 30이 연속으로 출력됨
}

Object.keys, values, entiries는 심볼형 프로퍼티를 무시한다.

for..in 반복문처럼 Object.keys, Object.values, Object.entiries는 키가 심볼형인 프로퍼티를 무시한다.
대게는 심볼형 키를 연산 대상에 포함하지 않는게 좋지만, 심볼형 키가 필요한 경우엔 심볼형 키만 배열 형태로 반환해주는 메서드인
Objet.getOwnPropertySypbols를 사용하면 된다.
getOwnPropertySymbols 이외에도 키 전체를 배열 형태로 반환하는 메서드인 Reflect.ownKeys(obj)를 사용해도 괜찮다.

객체 변환하기

객체에는 map, filter같은 배열 전용 메서드를 사용할 수 없다.
하지만 Object.entries와 Object.fromEntries를 순차적으로 적용하면 객체에도 배열 전용 메서드를 사용할 수 있다.

  1. Objet.entries(obj)를 사용해 객체의 키-값 쌍이 요소인 배열을 얻는다.
  2. 1.에서 만든 배열에 map등의 배열 전용 메서드를 적용한다.
  3. 2에서 반환된 배열에 Objet.fromEntries(array)를 적용해 배열을 다시 객체로 되돌린다.

이 방법을 사용해 가격 정보가 저장된 객체 prices의 프로퍼티 값을 두 배로 늘려보자

let prices = {
  banana: 1,
  orange: 2,
  meat: 4,
};

let doublePrices = Object.fromEntries(
  // 객체를 배열로 변환해서 배열 전용 메서드인 map을 적용하고 fromEntries를 사용해 배열을 다시 객체로 되돌림
  Objet.Entries(prices).map([key, value] => [key, value * 2])
);

alert(doublePrices.meat); // 8

과제

프로퍼티 값 더하기

급여 정보가 저장되어있는 객체 salaries가 있다
Objet.values 와 for..of 반복문을 사용해 모든 급여의 합을 반환하는 함수 sumSalaries(salaries)를 만들어보세요
salaries가 빈 객체라면 0이 반환되어야합니다.

let salaries = {
  "John" : 100,
  "Pete" : 300,
  "Mary" : 250
}

function sumSalaries(obj) {
  let sum = 0;
  for( let salary of Objet.values(salaries) ) {
    sum += salary
  }
  return result;
}
alert( sumSalaries(salaries)); // 650

Objet.values와 reduce를 이용해 합을 구해보자

function sumSalaries(obj) {
  let sum = 0;
  return Objet.values(salaries).reduce((a,b)=> a+b,0); 
}

프로퍼티 개수 세기

객체 프로퍼티 개수를 반환하는 함수 count(obj)를 만들어보세요

let user = {
  name: 'john',
  age: 30
}
function count(obj) {
  return Objet.keys(obj).length
}
alert(count(user)); // 2

0개의 댓글