본문 바로가기
Programming Language

JS33 - 24.컬렉션과 생성기(Collection and Constructor)

by 양찬우 2021. 6. 23.
728x90

 

컬렉션 Collection

 

JavaScript Collections

Collections 프로그래밍 언어에서 Collection 이란 단어는 '프로그래밍 언어가 제공하는 값을 담을 수 있는 컨테이너' 라고 생각합니다. 메이저 프로그래밍 언어에는 여러가지 데이터 컬렉션들이 존재

velog.io

  • 프로그래밍 언어가 제공하는 값을 담을 수 있는 컨테이너
    • Python - list, tuple, dictionaries …
    • Java - ArrayList, HashMap, HashSet, Queue, Stack …
    • Ruby - hashes, arrays
    • Javascript(기존 Arrays + Objects, 이후 ES6에서 새로 추가)
      • Indexed Collection - Arrays, Typed Array
      • Keyed Collection - Objects, Map, Set, Weak Map, Weak Set

Map

  • Key - Value 의 쌍으로 이루어진 컬렉션
  • 모든 유형의 데이터를 키로 사용할 수 있다.
  • 키-값 쌍을 추가하려면 .set() 메소드를 사용한다.
  • new를 통해 인스턴스화 할 수 있다.

메소드와 프로퍼티

  • new Map() – 맵을 생성한다.
  • map.set(key, value) – key와 value값을 저장한다.
  • map.get(key) – key가 map에 없는 경우 undefined로 값을 리턴한다.
  • map.has(key) – key가 있으면 true를 반환하고 그렇지 않으면 false를 리턴한다.
  • map.delete(key) – key값을 삭제한다.
  • map.clear() – map을 돌며 모두 삭제한다.
  • map.size – 최근 사용된 엘리먼트의 갯수를 리턴한다.
  • map에서 루프돌기 (Iteration)
    • map.keys() – key를 리턴한다.
    • map.values() – value를 리턴한다.
    • map.entries() – [key, value] 항목에 대해 허용 가능한 값을 리턴한다. 이 값은 기본적으로 for..of에서 사용된다.

일반 객체의 문제점

  • 객체의 키로 내장 메소드의 이름을 사용할 시 이름 충돌이 일어날 수 있습니다.
  • 속성의 Key는 항상 문자열 이어야 합니다. (ES6 에서는 심볼도 가능합니다.)
  • 객체에 얼마나 많은 속성이 존재하는지 알아낼 수 있는 효과적인 방법이 없습니다.(size나 length 같은 메서드가 없습니다.)
  • 일반 객체를 반복하려면 많은 비용이 소모됩니다.

여기서 가장 큰 문제점은 일반 객체는 iterable 하지 않기 때문에 iterable을 사용하는 모든 문법에 객체를 사용할 수 없습니다.

하지만 직면한 문제가 위의 기능들을 필요로 하지 않는다면, 일반 객체를 사용하는 것이 올바른 선택일 수 있습니다.

주의

  • ES6 컬렉션이 사용자 데이터와 빌트인 메소드 사이의 이름 충돌을 피하기 위해 설계 되었기 때문에, ES6 컬렉션은 자신의 멤버 데이터를 드러내기 위해 속성(property)를 사용하지 않습니다.
  • 이로 인해 해시 테이블의 멤버 데이터에 접근하기 위해 obj.key 또는 obj[key] 같은 구문을 사용할 수 없습니다. 객체의 값을 가져오기 위해서는 map.get(key) 구문을 이용해야하며 해시 테이블의 멤버 데이터는 속성과 달리 프로퍼티 체인(property chain)을 통해 상속되지 않습니다.
  • map은 키를 비교할 때 동등성을 테스트하기 위해 SameValueZero 알고리즘을 사용한다. 엄격한 평등 === 과 동일하지만, 차이점은 NaN이 NaN과 동등하다고 여겨진다는 것이다. 그래서 NaN도 key로 활용할 수 있다. 이 알고리즘은 변경하거나 사용자 정의할 수 없다.

사용 예시

const myMap = new Map; // new 생성자로 선언해서 사용할 수 있습니다.
myMap.set('yesdoing', 'looser');

const person = { age: '111', gender: 'none', name: 'yesdoing'};
const whoami = {};

myMap.set(person, whoami); // 객체를 키와 값으로 받을 수 있습니다.

for(const [key, value] of myMap) { // destructuring으로 값을 가져와서 쓸 수 있어요.
	console.log(`${key} = ${value}`);  // yesdoing = looser [object Object] = [object Object]
}

myMap.get(person); // {}

myMap.forEach(value, key) => { // 다른 forEach와의 호완을 위해서 value, key 순입니다.
	console.log(`${key} = ${value}`);  // yesdoing = looser [object Object] = [object Object]	
}, myMap);

myMap.clear(); // map에 있는 엔트리를 전부 삭제합니다. 
console.log(myMap.size); // 0

// 모든 map.set은 map 자체를 반환하므로, 다음 호출을 "체인"할 수 있다.
map.set('1', 'str1')
  .set(1, 'num1')
  .set(true, 'bool1');

// [[키,값]] 의 쌍으로 map을 생성가능
let map = new Map([
  ['1',  'str1'],
  [1,    'num1'],
  [true, 'bool1']
]);

// Object.fromEntries는 [key, value] 쌍의 배열을 지정하면 다음과 같은 방법으로 객체를 생성한다.
let prices = Object.fromEntries([
  ['banana', 1],
  ['orange', 2],
  ['meat', 4]
]);

Set

  • Set은 value를 키 값으로 갖는 컬렉션입니다.
  • Set은 수정 가능하며, 프로그램이 실행되는 동안 값의 추가나 삭제를 할 수 있습니다.
  • 각 값이 한 번만 발생할 수 있는 특수 유형 컬렉션
    • key가 존재하지 않는다.

속성과 메소드

  • new set(iterable) – 세트를 작성하고, 허용 가능한 개체(일반적으로 배열)가 제공된 경우, 해당 개체에서 세트로 값을 복사한다.
  • set.add(value) – 값을 추가하고 set 자체를 반환한다.
  • set.delete(value) – 값을 삭제하고, 호출 순간에 값이 존재하면 true를 반환하고, 그렇지 않으면 false를 반환한다.
  • set.has(value) – 값이 set에 있으면 true를 반환하고 그렇지 않으면 false를 반환한다.
  • set.clear() – 집합에서 모든 것을 제거한다.
  • set.size – 요소 개수.
  • set에서 루프돌기 (Iteration)
    • for..of 혹은 forEach
    • set.keys() – 값에 대해 허용 가능한 개체를 반환
    • set.values() – set.keys()와 동일하며, 지도와의 호환성
    • set.entries() – [value, value] 항목에 대해 허용 가능한 개체를 반환하여 map과 호환

값이 중복되지 않습니다.

  • 이미 존재하는 값을 추가하려고 하면 아무 일도 일어나지 않습니다.
  • const mySet = new Set(“abcd”); mySet.size; // 4 mySet.add(“a”); mySet.size; // 4

Set은 뚜렷한 목적을 가지고 데이터를 관리합니다.

바로 어떤 데이터가 자신의 멤버인지 확인하는 작업을 빨리 처리하려는 목적입니다.

const mySet = new Set("abcd");
const myArray = [..."abcd"];

myArray.indexOf("a") !== -1 // true slow
mySet.has("a")              // true fast

Set으로 할 수 없는 일

  • Set은 인덱스 값으로 데이터를 조회하는 일을 할 수 없습니다.
myArray[0]; // "a"
mySet[0];   // undefined

Set 메소드 사용예시

let mySet = new Set;  // 그냥 new 생성자로 빈 Set을 선언할 수 있습니다.
let iterSet = new Set([1, 2, 3]); // 생성할때 iterable한 객체를 초기 값으로 줄 수 있습니다.
console.log(iterSet.size);  // size로 Set의 크기를 알 수 있습니다.
console.log(iterSet.has(1)); // has로 Set에 값이 존재하는지 알 수 있습니다.
mySet.add(1).add(2).add(3); // add로 Set에 값을 추가합니다. 이것은 체이닝 될 수 있습니다.
mySet.delete(1).delete(2);  // delete로 Set에 값을 삭제할 수 있습니다. 역시 체이닝 될 수 있습니다. 
iterSet.forEach((value, value, iter) => v); // Array.prototype.forEach 와 같은 forEach를 제공합니다. 
iterSet.clear(); // Set의 모든 값을 삭제합니다.

Set에서 구현되지 않은 것.

  • Array.prototype에 구현된 map, filter, some, every등의 내장함수 미구현
  • 많은 값들을 한번에 처리할 수 있는 메소드들이 누락 (Ex. set.addAll(iterable), set.removeAll(iterable) 등등
  • Set에 Object 값이 들어오는 경우 레퍼런스(주소 값) 이 다르면 값이 같아도 다르게 처리됩니다.

물론 이것들은 ES6의 다른 문법들을 통해서 구현 가능합니다.

Weak Collections

Map과 Set이 참조하는 객체들은 강하게 연결되어 있습니다. 이 것은 JavaScript의 가비지 컬렉션이 메모리 수거를 못하게 막는 원인이 됩니다. 만약 크기가 큰 Map과 Set의 객체가 더 이상 쓰이지 않는다면 가비지 컬렉션에서 이것을 가져가기 위해 비싼 비용을 치뤄야 합니다.

이것을 해결하기 위해 ES6에서는 Weak Map, Weak Set이 나왔습니다.

이 컬렉션들은 더 이상 사용되지 않을 때, 메모리에서 쉽게 삭제 되기 위해 ‘약한’ 결합을 유지합니다.

Weak Map

Weak Map은 Map과 비슷합니다. 단지 메서드가 몇개 없고, 가비지 컬렉션의 처리가 다릅니다.

const yesdoing = new WeakMap(); // WeakMap을 생성합니다. 
const age = {}; // 키는 반드시 객체여야 합니다.
const job = {}; // 키는 반드시 객체여야 합니다.

yesdoing.set(age, 11111); // 키 - 값을 설정합니다.
yesdoing.set(job, 'air'); // 값으로는 어떤 타입이라도 들어올 수 있습니다. 

yesdoing.has(job); // True
yesdoing.delete(job) // key를 삭제합니다. 

_makeClone() {
  this._containerClone = this.container.cloneNode(true);
  this._cloneToNodes = new WeakMap();
  this._nodesToClones = new WeakMap();

  ...

  let n = this.container;
  let c = this._containerClone;

  // find the currentNode's clone
  while (n !== null) {
    if (n === this.currentNode) {
    this._currentNodeClone = c;
    }
    this._cloneToNodes.set(c, n);
    this._nodesToClones.set(n, c);

    n = iterator.nextNode();
    c = cloneIterator.nextNode();
  }

Weak Set

Weak Set 역시 Set과 비슷하지만 메서드가 몇개 없습니다.

const yesdoing = new WeakSet(); // WeakMap을 생성합니다. 
const age = {}; // 값은 반드시 객체여야 합니다. 

yesdoing.add(age); // 값을 추가합니다.

yesdoing.has(age); // True
yesdoing.delete(age) // 값을 삭제합니다

let isMarked     = new WeakSet()
let attachedData = new WeakMap()

export class Node {
    constructor (id)   { this.id = id                  }
    mark        ()     { isMarked.add(this)            }
    unmark      ()     { isMarked.delete(this)         }
    marked      ()     { return isMarked.has(this)     }
    set data    (data) { attachedData.set(this, data)  }
    get data    ()     { return attachedData.get(this) }
}

let foo = new Node("foo")

JSON.stringify(foo) === '{"id":"foo"}'
foo.mark()
foo.data = "bar"
foo.data === "bar"
JSON.stringify(foo) === '{"id":"foo"}'

isMarked.has(foo)     === true
attachedData.has(foo) === true
foo = null  /* remove only reference to foo */
attachedData.has(foo) === false
isMarked.has(foo)     === false
728x90

'Programming Language' 카테고리의 다른 글

JS33 - 26.Async/Await  (0) 2021.10.27
JS33 - 25.프로미스(Promise)  (0) 2021.06.24
JS33 - 23.재귀함수(Recursive Function)  (0) 2021.06.08
JS33 - 22.고차함수(Higher Order Function)  (0) 2021.06.08
JS33 - 21.클로져(Closure)  (0) 2021.06.08

댓글