모던 자바스크립트 Deep Dive : 33장 7번째 데이터 타입 Symbol

EdLee·2022년 11월 21일

javascript

목록 보기
23/37

33장 7번째 데이터 타입 Symbol

1. 심벌이란?


  • 변경 불가능한 원시 타입의 값
  • 다른 값과 중복되지 않은 유일무이한 값
  • 이름의 충돌 위험이 없는 유일한 프로퍼티 키를 만들기 위해 사용

2. 심벌 값의 생성


2.1 Symbol 함수

  • 리터럴 표기법 사용 불가능
  • 반드시 Symbol 함수를 호출해 생성해야 한다
// Symbol 함수를 호출하여 유일무이한 심벌 값을 생성한다.
const mySymbol = Symbol();
console.log(typeof mySymbol); // symbol

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

// new 생성자로 생성 불가
new Symbol(); // TypeError: Symbol is not a constructor

// 심벌 값에 대한 설명이 같더라도 유일무이한 심벌 값을 생성한다.
const mySymbol1 = Symbol('mySymbol');
const mySymbol2 = Symbol('mySymbol');

console.log(mySymbol1 === mySymbol2); // false

// 심벌 값은 암묵적으로 문자열이나 숫자 타입으로 변환되지 않는다.
console.log(mySymbol + ''); // TypeError: Cannot convert a Symbol value to a string
console.log(+mySymbol);     // TypeError: Cannot convert a Symbol value to a string
  • 단, boolean으로는 암묵적으로 타입 변환 가능
const mySymbol = Symbol();

// 불리언 타입으로는 암묵적으로 타입 변환된다.
console.log(!!mySymbol); // true

// if 문 등에서 존재 확인을 위해 사용할 수 있다.
if (mySymbol) console.log('mySymbol is not empty.');

2.2 Symbol.for / Symbol.keyFor 메서드

Symbol.for

  • 인수로 전달 받은 문자열을 키로 사용하여 키와 심벌 값의 쌍들이 저장되어 있는 전역 심벌 레지스트리에서 해당 키와 일치하는 심벌 값을 검색
  • 검색 성공 시, 검색된 심벌 값을 반환
  • 검색에 실패 시, 새로운 심벌 값을 생성하여 Symbol.for 메서드의 인수로 전달된 키로 전역 심벌 레지스트리에 저장한 후, 생성된 심벌 값을 반환
// 전역 심벌 레지스트리에 mySymbol이라는 키로 저장된 심벌 값이 없으면 새로운 심벌 값을 생성
const s1 = Symbol.for('mySymbol');
// 전역 심벌 레지스트리에 mySymbol이라는 키로 저장된 심벌 값이 있으면 해당 심벌 값을 반환
const s2 = Symbol.for('mySymbol');

console.log(s1 === s2); // true

Symbol.keyFor

  • 전역 심벌 레지스트리로부터 키를 추출해 제거
// 전역 심벌 레지스트리에 mySymbol이라는 키로 저장된 심벌 값이 없으면 새로운 심벌 값을 생성
const s1 = Symbol.for('mySymbol');
// 전역 심벌 레지스트리에 저장된 심벌 값의 키를 추출
Symbol.keyFor(s1); // -> mySymbol

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

3. 심벌과 상수


  • 심벌은 다음과 같이 활용이 가능
const Direction = {
  UP: Symbol('up'),
  DOWN: Symbol('down'),
  LEFT: Symbol('left'),
  RIGHT: Symbol('right')
};

const myDirection = Direction.UP;

if (myDirection === Direction.UP) {
  console.log('You are going UP.');
}

4. 심벌과 프로퍼티 키


  • 객체의 프로퍼티 키는 빈 문자열을 포함하는 모든 문자열 또는 심벌 값으로 만들 수 있으며 동적으로 생성도 가능
const obj = {
  // 심벌 값으로 프로퍼티 키를 생성
  [Symbol.for('mySymbol')]: 1
};

obj[Symbol.for('mySymbol')]; // 1
  • 위처럼 심벌로 프로퍼티 키를 만들면 다른 프로퍼티 키와 절대 충돌하지 않는다.

5. 심벌과 프로퍼티 은닉


  • 심벌은 for ...in 문이나 Object.keys, Object.getOwnPropertyNames 메서드로 찾을 수 없어서 은닉에 유용
const obj = {
  // 심벌 값으로 프로퍼티 키를 생성
  [Symbol('mySymbol')]: 1
};

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

console.log(Object.keys(obj)); // []
console.log(Object.getOwnPropertyNames(obj)); // []
  • ES6부터 Object.getOwnProtpertySymbols 메서드로 찾을 수 있다
const obj = {
  // 심벌 값으로 프로퍼티 키를 생성
  [Symbol('mySymbol')]: 1
};

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

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

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.redue((acc, cur) => acc + cur, 0);
};

[1,2][Symbol.for('sum')](); // 3

7. Well-known Symbol


function Symbol() { [native code] }
asyncIterator: symbol
for: ƒ for()
hasInstance: symbol
isConcatSpreadable: symbol
iterator: symbol
keyFor: ƒ keyFor()
length: 0
match: symbol
matchAll: symbol
name: "Symbol"
prototype: symbol
replace: symbol
search: symbol
species: symbol
split: symbol
toPrimitive: symbol
toStringTag: symbol
unscopables: symbol
__proto__: ƒ ()
  • well-known Symbol : 위처럼 JS가 기본 제공하는 빌트인 심볼
  • 이 심볼들은 JS 엔진 내부 알고리즘에 사용된다
  • 이를 이용해 일반 객체를 빌트인 객체처럼 사용할 수 있도록 만들 수 있다
const iterable = {
  [Symbol.iterator]() {
    let cur = 1;
    const max = 5;
    return {
      next() {
        return { value: cur++, done: cur > max + 1 };
      }
    };
  }
};

for(const num of iterable) {
  console.log(num); // 1 2 3 4 5
}

0개의 댓글