FrontEnd/Deep Dive

[JS] DeepDive(33) 7번째 데이터 타입 Symbol

728x90

심벌은 ES6에서 도입된 7번째 데이터 타입으로 변경 불가능한 원시 타입의 값이다.

 

 

 

심벌값은 Symbol함수를 호출하여 생성한다.이때 생성된 심벌 값은 외부로 노출되지 않아 확인할 수 없으며, 다른값과 절대 중복되지 않은 유일무이한 값이다.

 

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

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

 

 

언뜻보면 생성자 함수로 객체를 생성하는 것처럼 보이지만 new 연산자와 함께 호출하지 않으며 선택적으로 문자열을 인수로 전달할 수 있다.

 

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

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

 

심벌값은 암묵적으로 문자열이나 숫자 타입으로 변환되지 않는다.

 

const mySymbol = Symbol();

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

 

 

 

단, 불리언 타입으로는 암묵적으로 타입 변환된다.

 

 

const mySymbol = Symbol();

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

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

 

 

 

Symbol.for / Symbol.keyFor 메서드

 

Symbol.for 메서드는 인수로 전달받은 문자열을 키로 사용하여 키와 심벌 값의 쌍들이 저장되어 있는 전역 심벌 레지스트리에서 해당 키와 일치하는 심벌 값을 검색한다.

 

// 전역 심벌 레지스트리에 mySymbol이라는 키로 저장된 심벌 값이 없으면 새로운 심벌 값을 생성
const s1 = Symbol.for('mySymbol');
// 전역 심벌 레지스트리에 mySymbol이라는 키로 저장된 심벌 값이 있으면 해당 심벌 값을 반환
const s2 = Symbol.for('mySymbol');

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

 

 

Symbol 함수는 호출될때마다 유일무이한 심벌 값을 생성한다.만약 해당 값이 있다면 해당 심벌 값을 반환하며 없다면 새로운 심벌을 생성한다.

 

Symbol.keyFor 메서드를 호출하면 전역 심벌 레지스트리에 저장된 심벌 값의 키를 추출할 수 있다.

 

 

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

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

 

 

 

심벌과 상수

상하좌우를 나타내는 상수를 정의해본다 생각해보자.

 

// 위, 아래, 왼쪽, 오른쪽을 나타내는 상수를 정의한다.
// 이때 값 1, 2, 3, 4에는 특별한 의미가 없고 상수 이름에 의미가 있다.
const Direction = {
  UP: 1,
  DOWN: 2,
  LEFT: 3,
  RIGHT: 4
};

// 변수에 상수를 할당
const myDirection = Direction.UP;

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

 

위경우 상수 이름 자체에 의미가 있는 경우 상수값이 변경될 수 있으며 다른 변수 값과 중복될 수 있다는 문제가 있다. 이런 경우에 심벌을 사용하면 좋다.

 

 

// 위, 아래, 왼쪽, 오른쪽을 나타내는 상수를 정의한다.
// 중복될 가능성이 없는 심벌 값으로 상수 값을 생성한다.
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.');
}

 

 

 

심벌과 프로퍼티 키

객체의 프로퍼티 키는 빈 문자열을 포함하는 모든 문자열 또는 심벌 값으로 만들 수 있으며 동적으로 생성할 수 있다.

 

const obj = {
  // 심벌 값으로 프로퍼티 키를 생성
  [Symbol.for('mySymbol')]: 1
};

obj[Symbol.for('mySymbol')]; // -> 1

 

 

심벌과 프로퍼티 은닉

 

심벌 값을 프로퍼티 키로 사용하여 생성한 프로퍼티는 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)); // []

 

위 방식을 사용해도 프로퍼티를 완전히 숨길 수 있는것은 아니다.

 

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

 

 

 

심벌과 표준 빌트인 객체 확장

 

 

 

일반적으로 표준 빌트인 객체에 사용자 정의 메서드를 직접 추가하는것은 좋지 않으며 읽기전용으로 사용하는 것이 좋다.

 

// 표준 빌트인 객체를 확장하는 것은 권장하지 않는다.
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

 

 

 

 

728x90

'FrontEnd > Deep Dive' 카테고리의 다른 글

[JS] DeepDive(35) 스프레드 문법  (0) 2023.09.16
[JS] DeepDive(34) 이터레이션 프로토콜  (0) 2023.09.12
[JS] DeepDive(32) String  (0) 2023.09.06
[JS] DeepDive(31) RegExp  (0) 2023.09.05
[JS] DeepDive(30) Date  (0) 2023.09.04