모던 자바스크립트 Deep Dive 33-

eunn·2024년 2월 12일
0

33. Symbol

33.1 심볼이란?

  • ES6에서 도입된 7번째 데이터 타입으로 변경 불가능한 원시 타입의 값
  • 이름의 충돌 위험이 없는 유일한 프로퍼티 키를 만들기 위해 사용

33.2 심볼 값의 생성

// Symbol 함수를 호출하여 유일무이한 심볼 값을 생성
const mySymbol = Symbol()
console.log(typeof mySymbol); // symbol

// 심볼 값은 외부로 노출되지 않아 확인할 수 없다.
console.log(mySymbol); // Symbol()

new Symbol(); // TypeError: Symbol is not a constructor
  • Symbol 함수에는 선택적으로 문자열을 인수로 전달할 수 있다. -> 디버깅 용도로만 사용
// 심볼 값에 대한 설명이 같더라도 유일무이한 심볼 값을 생성
const mySymbol1 = Symbol('mySymbol');
const mySymbol2 = Symbol('mySymbol');

console.log(mySymbol1 === mySymbol); // false
  • Symbol.keyFor 메서드를 사용하면 전역 심볼 레지스트리에 저장된 심볼 값의 키를 추출할 수 있다.
// 전역 심볼 레지스트리에 mySymbol이라는 키로 저장된 심볼 값이 없으면 새로운 심볼 값을 생성
const s1 = Symbol.for('mySymbol')
// 전역 심볼 레지스트리에 저장된 심볼 값의 키를 추출
Symbol.keyFor(s1); // -> mySymbol

// Symbol 함수를 호출하여 생성한 심볼 값은 전역 심볼 레지스트리에 등록되어 관리되지 않는다.
const s2 = Symbol('foo')
// 전역 심볼 레지스트리에 저장된 심볼 값의 키를 추출
Symbol.keyFor(s2); // -> undefined

33.3 심볼과 상수

// 위, 아래, 왼쪽, 오른쪽을 나타내는 상수를 정의한다.
// 중복될 가능성이 없는 심볼 값으로 상수 값을 생성한다.
const Direction = {
  UP: Symbol('up');
  DOWN: Symbol('down');
  LEFT: Symbol('left');
  RIGHT: Symbol('right');
};  

enum

  • enum은 명명된 숫자 상수의 집합으로 열거형이라 부른다.
  • 자바스크립트는 enum을 지원하지 않는다.
  • Object.freeze 메서드와 함께 심볼 값을 사용해 객체의 변경을 방지할 수 있다.
// Javascript enum
// Direction 객체는 불변 객체이며 프로퍼티 값은 유일무이한 값이다.
const Direction = Object.freeze({
  UP: Symbol('up');
  DOWN: Symbol('down');
  LEFT: Symbol('left');
  RIGHT: Symbol('right');
});  

33.4 심볼과 프로퍼티 키

  • 심볼 값은 유일무이한 값이므로 심볼 값으로 프로퍼티 키를 만들면 다른 프로퍼티 키와 절대 충돌하지 않는다.
const obj = {
  // 심볼 값으로 프로퍼티 키를 생성
  [Symbol.for('mySymbol')]: 1
};
obj[Symbol.for('mySymbol')]; // -> 1

33.5 심볼과 프로퍼티 은닉

  • 심볼 값을 프로퍼티 키로 사용하여 생성한 프로퍼티는 for ... in 문이나 Object.keys, Object.getOwnPropertyNames 메서드로 찾을 수 없다.
  • 외부에 노출할 필요 없는 프로퍼티를 은닉할 수 있다.
  • Object.getOwnPropertySymbols 메서드를 사용하면 프로퍼티를 찾을 수 있다.
const obj = {
  // 심볼 값으로 프로퍼티 키를 생성
  [Symbol.for('mySymbol')]: 1
};

for (const key in obj) {
  console.log(key); // 아무것도 출력되지 않는다.
}
console.log(Object.keys(obj)); // []
console.log(Object.getOwnPropertyNames(obj)); // []

// getOwnPropertySymbols 메서드는 인수로 전달한 객체의 심볼 프로퍼티 키를 배열로 반환한다.
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(mySymbol)]

// getOwnPropertySymbols 메서드로 심볼 값을 찾을 수 있다.
const symbolkey1 = Object.getOwnPropertySymbols(obj)[0];
console.log(obj[symbolKey1]); // 1

33.6 심볼과 표준 빌트인 객체 확장

  • 표준 빌트인 객체는 읽기 전용으로 사용 -> 직접 추가한 메서드와 미래에 표준 사양으로 추가될 메서드의 이름이 중복될 수 있기 때문
  • 심볼 값으로 프로퍼티 키를 생성하여 표준 빌트인 객체를 확장하면 충돌 위험 없이 표준 빌트인 객체를 확장할 수 있다.
// 표준 빌트인 객체를 확장하는 것은 권장하지 않는다.
Array.prototype.sum = function () {
  return this.reduce((acc, cur) => acc + cur, 0);
};
[1,2].sum(); // -> 3

// 심볼 값으로 프로퍼티 키를 동작 생성하면 다른 프로퍼티 키와 절대 충돌하지 않아 안전하다.
Array.prototype[Symbol.for('sum')] = function() {
  return this.reduce((acc, cur) => acc + cur, 0);
};
[1,2][Symbol.for('sum')](); // -> 3

33.7 well-known Symbol

  • 자바스크립트가 기본 제공하는 빌트인 심볼 값을 ECMAScript 사양에서는 Well-known Symbol이라 부른다.
  • 심볼은 중복되지 않는 상수 값을 생성, 기존에 작성된 코드에 영향을 주지 않고 새로운 프로퍼티를 추가하기 위해 도입(하위 호환성 보장)
// 1~5 범위의 정수로 이루어진 이터러블
const iterable = {
  // Symbol.iterator 메서드를 구현하여 이터러블 프로토콜을 준수
  [Symbol.iterator]() {
    let cur = 1;
    const max = 5;
    // Symbol.iterator 메서드는 next 메서드를 소유한 이터레이터를 반환
    return. {
      next() {
        return { value: cur++, done: cur > max + 1 };
      }
    };
  }
};
for (const num of iterable) {
  console.log(num); // 1 2 3 4 5

34 이터러블

34.1 이터레이션 프로토콜

  • 이터러블 프로토콜: 이터러블 프로토콜을 준수한 객체를 이터러블이라 한다. 이터러블은 for ..of 문으로 순회할 수 있으며 스프레드 문법과 티스트럭쳐링 할당의 대상으로 사용할 수 있다.
  • 이터레이터 프로토콜: 이터레이터는 next 메서드를 소유, next 메서드를 호출하면 이터러블을 순회하며 value 와 done 프로퍼티를 갖는 이터레이터 리절트 객체를 반환한다.
    이터레이터 프로토콜을 준수한 객채를 이터레이터라 하며, 이터레이터는 이터러블의 요소를 탐색하기 위한 포인터 역할을 한다.

34.1.1 이터러블

  • Symbol.iterator를 프로퍼티 키로 사용한 메서드를 직접 구현하거나 프로토타입 체인을 통해 상속받은 객체
const isIterable = v => v !== null && typeof v[Symbol.iterator] === 'function';

// 배열, 문자열, Map, Set 등은 이터러블이다.
isIterable([]); // -> true
isIterable(''); // -> true
isIterable(new Map()); // -> true
isIterable(new Set()); // -> true
isIterable({}); // -> false
  • 배열은 Array.prototype 의 Symbol.iterator 메서드를 상속받는 이터러블
  • for ...of 문으로 순회할 수 있으며, 스프레드 문법과 배열 디스트럭처링 할당의 대상으로 사용할 수 있다.
  • Symbol.iterator 메서드를 직접 구현하지 않거나 상속받지 않은 일반 객체는 이터러블 프로토콜을 준수한 이터러블이 아니다.
  • 일반 객체는 for ...of 문으로 순회할 수 없으며 스프레드 문법과 배열 디스트럭처링 할당의 대상으로 사용할 수 없다.
const array = [1, 2, 3];
const obj = { a: 1, b: 2 };

// 배열은 Array.prototype의 Symbol.iterator 메서드를 상속받는 이터러블이다.
console.log(Symbol.iterator in array); // true
// 일반 객체는 Symbole.iterator 메서드를 구현하거나 상속받지 않는다.
console.log(Symbol.iterator in obj); // false

// 이터러블인 배열은 for ...of 문으로 순회 가능하다.
for (const iten of array) {
  console.log(item);
}
// 이터러블이 아닌 일반 객체는 for ...of 문으로 순회할 수 없다.
for (const item of obj) { // -> TypeError: obj is not iterable
  console.log(item);
}

// 이터러블인 배열은 스프레드 문법의 대상으로 사용할 수 있다.
console.log([...array]); // [1, 2, 3]
// 스프레드 프로퍼티 제안(Stage 4)은 객체 리터럴 내부에서 스프레드 문법의 사용을 허용한다.
console.log({ ...obj}); // {a: 1, b: 2}

// 이터러블인 배열은 배열 디스트럭처링 할당의 대상으로 사용할 수 있다.
const [a, ...rest] = array;
console.log(a, rest); // 1, [2, 3]
// 이터러블이 아닌 일반 객체는 배열 디스트럭처링 할당의 대상으로 사용할 수 없다.
const [a, b] = obj; // -> TypeError: obj is not iterable

34.1.2 이터레이터

  • 이터러블의 Symbol.iterator 메서드가 반환한 이터레이터는 next 메서드를 갖는다.
  • next 메서드를 호출하면 이터러블을 순차적으로 한 단계식 순호히하며 순회 결과를 나타내는 이터레이터 리절트 객체를 반환한다.
// 배열은 이터러블 프로토콜을 준수한 이터러블이다.
const array = [1, 2, 3];

// Symbol.iterator 메서드는 이터레이터를 반환한다. 이터레이터는 next 메서드를 갖는다.
const iterator = array[Symbol.iterator]();

// next 메서드를 호출하면 이터러블을 순회하여 순회 결과를 나타내는 이터네이터 리절트 객체를 반환한다.
// 이터레이터 리절트 객체는 value 와 done 프로퍼티를 갖는 객체다.
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

34.3 for...of 문

for (변수선언문 of 이터러블) { ... }
for (변수선언문 in 객체) { ... }

// 이터러블
const iterable = [1, 2, 3];
                   
// 이터러블의 Symbol.iterator 메서드를 호출하여 이터레이터를 생성한다.
const iterator = iterable[Symbol.iterator]();

for (;;) {
  // 이터레이터의 next 메서드를 호출하여 이터러블을 순회한다.
  // 이때 next 메서드는 이터레이터 리절트 객체를 반환한다.
  const res = iterator.next();
  
  // next 메서드가 반환한 이터레이터 리절트 객체의 done 프로퍼티 값이 true이면 이터러블의 순회를 중단한다.
  if (res.done) break;
  
  // 이터레이터 리절트 객체의 value 프로퍼티 값을 item 변수에 할당한다.
  const item = res.value;
  console.log(item); // 1 2 3

34.4 이터러블과 유사 배열 객체

  • 유사 배열 객체는 배열처럼 인덱스로 프로퍼티 값에 접근할 수 있고 length 프로퍼티를 갖는 객체를 말한다.
  • 유사 배열 객체를 이터러블이 아닌 일반 객체이므로, Symbole.iterator 메서드가 없기 때문에 for...of 문으로 순회할 수 없다.
    - but, arguments, NodeList, HTMLCollection은 유사 배열 객체이면서 이터러블
// 유사 배열 객체
const arrayLike = {
  0: 1,
  1: 2,
  2: 3,
  length: 3
};

// Array.from 은 유사 배열 객체 또는 이터러블을 배열로 반환한다.
const arr = Array.from(arrayLike);
console.log(arr); // [1, 2, 3]

34.6 사용자 정의 이터러블

34.6.1 사용자 정의 이터러블 구현

// 피보나치 수열을 구현한 사용자 정의 이터러블
const fibonacci = {
  // Symbol.iterator 메서드를 구현하여 이터러블 프로토콜을 준수한다.
  [Symbol.iterator]() {
    let [pre, cur] = [0, 1];
    const max = 10; // 수열의 최대값
    
    // Symbol.iterator 메서드는 next 메서드를 소유한 이터레이터를 반환해야 하고
    // next 메서드는 이터레이터 리절트 객체를 반환해야 한다.
    return {
      next() {
        [pre, cur] = [cur, pre + cur];
        // 이터레이터 리절트 객체를 반환한다.
        return { value: cur, done: cur >= max };
      }
    };
  }
};

// 이터러블인 fibonacci 객체를 순회할 때마다 next 메서드가 호출된다.
for (const num of fibonacci) {
  console.log(num); // 1 2 3 5 8
}

35 스프레드 문법

  • ES6에서 도입된 스프레드 문법(전개 문법) ...은 여러 값들의 집합을 펼쳐 개별적인 값들의 목록으로 만든다.
  • 스프레드 문법의 결과물은 값으로 사용할 수 없고, 함수 호출문의 인수 목록, 배열 리터럴의 요소 목록, 객체 리터럴의 프로퍼티 목록

35.1 함수 호출문의 인수 목록에서 사용하는 경우

var arr = [1, 2, 3];

// apply 함수의 2번째 인수(배열)는 apply 함수가 호출하는 함수의 인수 목록이다.
// 따라서 배열이 펼쳐져서 인수로 전달되는 효과가 있다.
var max = Math.max.apply(null, arr); // -> 3
// 스프레드 문법을 사용하여 배열 arr을 1, 2, 3으로 펼쳐서 Math.max 에 전달
const max = Math.max(...arr); // -> 3
  • 스프레드 문법과 Rest 파라미터와 형태가 동일하여 혼동할 수 있으므로 주의 필요
// Rest 파라미터는 인수들의 목록을 배열로 전달받는다.
function foo(...rest) {
  console.log(rest); // 1, 2, 3 -> [1, 2, 3]
}

// 스프레드 문법은 배열과 같은 이터러블을 펼쳐서 개별적인 값들의 목록을 만든다.
// [1, 2, 3] -> 1, 2, 3
foo(...[1, 2, 3]);

35.2.4 이터러블을 배열로 변환

ES5에서 이터러블을 배열로 변환하려면 Function.prototype.apply 또는 Function.prototype.call 메서드를 사용하여 slice 메서드를 호출

// ES5
function sum() {
  // 이터러블이면서 유사 배열 객체인 arguments를 배열로 변환
  var args = Array.prototype.slice.call(arguments);
  
  return args.reduce(function (pre, cur)) {
    return pre + cur;
  }, 0);
}

console.log(sum(1, 2, 3)); // 6

// 이터러블이 아닌 유사 배열 객체
const arrayLike = {
  0: 1,
  1: 2,
  2: 3,
  length: 3
};

const arr = Array.prototype.slice.call(arrayLike); // -> [1, 2, 3]
console.log(Array.isArray(arr)); // true

35.3 객체 리터럴 내부에서 사용하는 경우

// 스프레드 프로퍼티
// 객체 복사(얕은 복사)
const obj = { x: 1, y: 2 };
const copy = { ...obj };
console.log(copy); // { x: 1, y: 2 }
console.log(obj === copy); // false

// 객체 병합 
const merged = { x: 1, y: 2, ...{ a: 3, b: 4 } };
console.log(merged); // { x: 1, y: 2, a: 3, b: 4 };

// 객체 병합. 프로퍼티가 중복되는 경우 뒤에 위치한 프로퍼티가 우선권을 갖는다.
const merged = Object.assign({}, { x: 1, y: 2 }, { y: 10, z: 3 }
console.log(merged); // { x: 1, y: 10, z: 3 }

36 디스트럭처링 할당

36.1 배열 디스트럭처링 할당

  • 배열 디스트럭처링 할당의 대상(할당문의 우변)은 이터러블이어야 하며, 할당 기준은 배열의 인덱스다.
const [x, y] = [1, 2];
const [x, y]; // SyntaxError: Missing initializer in destructuring declaration
const [a, b] = {}; // TypeError: {} is not iterable

// 배열 디스트럭처링 할당의 변수 선언과 할당을 분리할 수 있지만, const 키워드로 변수를 선언할 수 없으므로 권장하지 않는다.
let x, y;
[x, y] = [1, 2]

36.2 객체 디스트럭처링 할당

37 Set과 Map

37.1 Set

  • Set 객체는 중복되지 않는 유일한 값들의 집합이다.
  • 배열과 유사하지만 다음과 같은 차이가 있다.

37.1.1 Set 객체의 생성

  • Set 객체를 Set 생성자 함수로 생성. Set 생성자 함수에 인수를 전달하지 않으면 빈 Set 객체가 생성된다.
  • Set 생성자 함수는 이터러블을 인수로 전달받아 Set 객체를 생성한다. 이터러블의 중복된 값은 Set 객체에 요소로 저장되지 않는다.
const set = new Set();
console.log(set); // Set(0) {}

const set1 = new Set([1, 2, 3, 3]);
console.log(set1); // Set(3) {1, 2, 3}

const set2 = new Set('hello');
console.log(set2); // Set(4) {"h", "e", "l", "o" }
  • 중복을 허용하지 않는 Set 객체의 특성을 활용하여 배열에서 중복된 요소를 제거할 수 있다.
// 배열의 중복 요소 제거
const uniq = array => array.filter((v, i, self) => self.indexOf(v) === i);
console.log(uniq([2, 1, 2, 3, 4, 3, 4])); // [2, 1, 3, 4]

// Set을 사용한 배열의 중복 요소 제거
const uniq = array => [...new Set(array)];
console.log(uniq([2, 1, 2, 3, 4, 3, 4])); // [2, 1, 3, 4] 

37.1.2 요소 개수 확인

  • Set 객체의 요소 개수를 확인할 때는 Set.prototype.size 프로퍼티 사용
  • size 프로퍼티는 getter 함수만 존재하는 접근자 프로퍼티 (size 프로퍼티에 숫자를 할당하여 Set 객체의 요소 개수를 변경할 수 없다.)
const { size } = new Set([1, 2, 3, 3]);
console.log(size); // 3

37.1.3 요소 추가

  • Set 객체에 요소를 추가할 때는 Set.prototype.add 메서드를 사용한다.
  • Set 객체에 중복된 요소의 추가는 허용되지 않는다. (에러 발생하지 않고 무시된다.)
  • 일반 비교 연산자 ===을 사용하면 NaN과 NaN을 다르다고 평가한다. 하지만 Set 객체는 NaN과 NaN을 같다고 평가하여 중복 추가를 허용하지 않는다.
  • +0과 -0은 일치 비교 연산자 ===와 마찬가지로 같다고 평가하여 중복 추가를 허용하지 않는다.
  • Set 객체는 객체나 배열, 함수같이 자바스크립트의 모든 값을 요소로 저장할 수 있다.
const set = new Set();
console.log(set); // Set(0) {}

set.add(1).add(2).add(2);
console.log(set); // Set(2) {1, 2}

console.log(NaN === NaN); // false
console.log(0 === -0); // true

// NaN과 NaN을 같다고 평가하여 중복 추가를 허용하지 않는다.
set.add(NaN).add(NaN);
console.log(set); // Set(1) {NaN}

// +0과 -0을 같다고 평가하여 중복 추가를 허용하지 않는다.
set.add(0).add(-0);
console.log(set); // Set(2) {NaN, 0}

37.1.4 요소 존재 여부 확인

const set = new Set([1, 2, 3]);

console.log(set.has(2)); // true
console.log(set.has(4)); // false

37.1.5 요소 삭제

  • delete 메서드는 삭제 성공 여부를 나타내는 불리언 값을 반환한다. 따라서 Set.prototype.add 메서드와 달리 연속적으로 호출할 수 없다.
const set = new Set([1, 2, 3]);

// 요소 2를 삭제한다.
set.delete(2);
console.log(set); // Set(2) {1, 3}

// 요소 1을 삭제한다.
set.delete(1);
console.log(set); // Set(3) {1}

set.delete(1).delete(2); // TypeError: set.delete(...).delete is not a function

37.1.6 요소 일괄 삭제

  • 일괄 삭제는 Set.prototype.clear 메서드를 사용한다. clear 메서드는 언제나 undefined를 반환한다.
set.clear();
console.log(set); // Set(0) {}

37.1.7 요소 순회

  • Set 객체의 요소를 순회하려면 Set.prototype.forEach 메서드를 사용한다.
  • Set 객체는 이터러블이다. 따라서, for...of 문으로 순회할 수 있으며, 스프레드 문법과 배열 디스트럭처링의 대상이 될 수도 있다.
const set = new Set([1, 2, 3]);

// Set 객체는 Set.prototype의 Symbol.iterator 메서드를 상속받는 이터러블이다.
console.log(Symbol.iterator in set); // true

// 이터러블인 Set 객체는 for...of 문으로 순회할 수 있다.
for (const value of set) {
  console.log(value); // 1 2 3
}

// 이터러블인 Set 객체는 스프레드 문법의 대상이 될 수 있다.
console.log([...set]); // {1, 2, 3}

// 이터러블인 Set 객체는 배열 디스트럭처링 할당의 대상이 될 수 있다.
const [a, ...rest] = set;
console.log(a, rest); // 1, [2, 3]

37.1.8 집합 연산

  • Set 객체는 수학적 집합을 구현하기 위한 자료구조다.
  • Set 객체를 통해 교집합, 합집합, 차집합 등을 구현할 수 있다.

37.2 Map

  • Map 객체는 키와 값의 쌍으로 이루어진 컬렉션이다. Map 객체는 객체와 유사하지만 다음과 같은 차이가 있다.

37.2.1 Map 객체의 생성

  • Map 생성자 함수의 인수로 전달한 이터러블에 중복된 키를 갖는 요소가 존재하면 값이 덮어써진다. 따라서 Map 객체에는 중복된 키를 갖는 요소가 존재할 수 없다.
const map = new Map();
console.log(map); // Map(0) {}

const map1 = new Map([['key1', 'value1'], ['key2', 'value2']]);
console.log(map1); // Map(2) {"key1" => "value1", "key2" => "value2"}

const map2 = new Map([1, 2]); // TypeError: Iterator value 1 is not an entry object

const map = new Map([['key1', 'value1'], ['key1', 'value2']]);
console.log(map); // Map(1) {"key1" => "value2"}

37.2.2 요소 개수 확인

  • Map.prototype.size 프로퍼티 사용
  • size 프로퍼티는 getter 함수만 존재하는 접근자 프로퍼티다. 따라서 size 프로퍼티에 숫자를 할당하여 Map 객체의 요소 개수를 변경할 수 없다.
const map = new Map([['key1', 'value1'], ['key2', 'value2']]);

console.log(Object.getOwnPropertyDescription(Map.prototype, 'size'));
// {set: undefined, enumerable: false, configurable: true, get: f}

map.size = 10; // 무시된다.
console.log(map.size); // 2

37.2.3 요소 추가

  • Map.prototype.set 메서드를 사용
  • set 메서드는 새로운 요소가 추가된 Map 객체를 반환한다. 따라서 set 메서드를 호출한 후에 set 메서드를 연속적으로 호출할 수 있다.
  • 객체는 문자열 또는 심벌 값만 키로 사용 가능, Map 객체는 키 타입에 제한이 없다.
const map = new Map();

map.set('key1', 'value1').set('key2', 'value2')
cosnole.log(map); // Map(2) {'key1' => 'value1', 'key2' => 'value2'}

37.2.4 요소 취득

  • Map 객체에서 특정 요소를 취득하려면 Map.prototype.get 메서드를 사용
  • Map 객체에서 인수로 전달한 키를 갖는 요소가 존재하지 않으면 undefined를 반환

37.2.5 요소 존재 여부 확인

  • Map.prototype.has 메서드를 사용
  • has 메서드는 특정 요소의 존재 여부를 나타내는 불리언 값을 반환

37.2.6 요소 삭제

  • Map.prototype.delete 메서드를 사용
  • delete 메서드는 삭제 성공 여부를 나타내는 불리언 값을 반환 -> set 메서드와 달리 연속적으로 호출할 수 없다.
  • 존재하지 않는 키로 Map 객체의 요소를 삭제하려 하면 에러 없이 무시된다.

37.2.7 요소 일괄 삭제

  • Map.prototype.clear 메서드를 사용 -> undefined를 반환

37.2.8 요소 순회

  • Map.prototype.forEach 메서드 사용
profile
사람-컴퓨터와 소통하고 싶은 병아리 개발자입니다🐥

0개의 댓글