Javascript/Javascript

[Javascript] Symbol

금요일인줄 2025. 6. 25. 09:00
728x90
반응형



Symbol이란?


1997년 자바스크립트가 ECMAScript로 처음 표준화된 이래로 자바스크립트는 6개의 타입을 가지고 있었다.


[원시 타입(primitive data type)]

  • Boolean
  • null
  • undefined
  • Number
  • String


[객체 타입(Object type)]

  • Object



심볼(symbol)은 ES6에서 새롭게 추가된 7번째 타입으로 변경 불가능한 원시 타입의 값이다. 주로 이름의 충돌 위험이 없는 유일한 객체의 프로퍼티 키(property key)를 만들기 위해 사용한다.


 

Symbol의 생성


심볼은 아래의 3가지 방법으로 생성할 수 있다.

  • Symbol()
  • Symbol.for()
  • Symbor.iterator



Symbol()


Symbol은 Symbol() 함수로 생성할 수 있다. 이때 생성된 심볼은 객체가 아니라 변경 불가능한 원시 타입의 값이다. Symbol()은 String, Number, Boolean 등 wrapper 객체를 생성하는 생성자 함수와는 달리 new 연산자를 사용하지 않는다.

let mySymbol = Symbol();

let mySymbol2 = Symbol("something");
// Symbol()에 문자열을 줄수 있는데 별다른 뜻은 없고 일종의 주석 같은 개념이다. 
// 디버깅할 때 이 심볼이 어느 심볼인지 구분하기 편하게 하기 위한 장치

console.log(mySymbol);        // Symbol()
console.log(typeof mySymbol); // symbol


심볼은 유일성이 보장되는 자료형이기 때문에 설명이 동일한 심볼을 여러 개 만든다 해도 각 심볼 값은 다르다. 심볼에 붙이는 설명(심볼 이름)은 어떤 것에도 영향을 주지 않는 이름표 역할만 하는 것이다.

let id1 = Symbol("id");
let id2 = Symbol("id");

alert(id1 == id2); // false

 


 

Symbol.for()


Symbol()이 고유한 심볼을 반환한다면 Symbol.for() 는 전역으로 존재하는 global symbol table의 목록을 참조한다.

그러므로 Symbol.for(token string)으로 정의할 때 token string으로 정의 된 심볼이 있다면 해당 심볼을 반환하게 된다.

// 전역 Symbol 레지스트리에 foo라는 키로 저장된 Symbol이 없으면 새로운 Symbol 생성
const s1 = Symbol.for('foo');

// 전역 Symbol 레지스트리에 foo라는 키로 저장된 Symbol이 있으면 해당 Symbol을 반환
const s2 = Symbol.for('foo');

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

 


 

Symbol.keyFor()


Symbol.keyFor()global symbol table로부터 존재하는 Symbol의 token string을 반환한다.

var token = Symbol.for("tokenString");
console.log(Symbol.keyFor(token) === "tokenString"); // true

 


 

Symbol.description


Symbol.keyFor()가 전역 심볼의 이름을 반환한다면 Symbol.description은 일반 심볼 이름을 반환한다.

const shareSymbol = Symbol.for('myKey');
console.log(Symbol.keyFor(shareSymbol)); // myKey

const unsharedSymbol = Symbol('myKey');
console.log(Symbol.keyFor(unsharedSymbol)); // undefined
console.log(unsharedSymbol.description); // myKey

 

Symbol()이 매번 다른 Symbol 값을 생성하는 것에 반해 Symbol.for() 메서드는 하나의 심볼을 생성하여 여러 모듈이 key를 통해 같은 심볼을 공유할 수 있다.
Symbol.for()를 통해 생성된 심볼 값은 반드시 key를 갖는다. 이에 반해 Symbol()을 통해 생성된 심볼 값은 key가 없다.

 


 

Symbol의 사용


객체 프로퍼티의 key는 빈 문자열을 포함하는 모든 문자열로 만들 수 있다.

const obj = {};
const v = "name";

obj[v] = 'myProp';
obj[123] = 123; // 123은 문자열로 변환된다.
// obj.123 = 123;  // SyntaxError: Unexpected number
obj['prop' + 123] = false;

console.log(obj); 
/* 
{ 
  name : 'myProp', 
  '123' : 123, 
  prop123 : false 
}
*/


Symbol 값 또한 객체의 프로퍼티 key로 사용할 수 있다. Symbol은 유일한 값이므로 Symbol을 key로 갖는 프로퍼티는 다른 어떠한 프로퍼티와도 충돌하지 않는다.

const obj = {};

const mySymbol = Symbol('mySymbol');
const mySymbol2 = Symbol('mySymbol');

obj[mySymbol] = 123;
obj[mySymbol2] = 456;

console.log(obj); // { [Symbol(mySymbol)] : 123, Symbol(mySymbol)] : 456}
console.log(obj[mySymbol]); // 123

 


 

심볼은 문자형으로 자동 형변환되지 않는다.


자바스크립트에서는 문자형으로의 암시적 형변환이 비교적 자유롭게 일어나는 편이다. alert() 함수가 거의 모든 값을 인자로 받을 수 있는 이유가 바로 이 때문이다.

 

그러나 심볼의 경우 예외다. 심볼형의 값은 다른 자로형으로 암시적 형변환(자동 형변환)되지 않는다. 따라서 아래 예시에서 alert는 에러를 발생시킨다.

let id = Symbol("id"); 
alert(id); // TypeError: Cannot convert a Symbol value to a string

 

문자열과 심볼은 근본이 다르기 때문에 우연이라도 서로의 타입으로 변환되어서는 안 된다. 자바스크립트에서는 '언어 차원의 보호장치(language guard)'를 마련해 심볼형이 다른 자료형으로 변환되지 않도록 막아준다. 심볼을 반드시 출력해 줘야 하는 상황이라면 아래와 같이 toString() 메서드를 명시적으로 호출해 주면 된다.

let id = Symbol("id");
alert(id.toString()); // Symbol(id)가 얼럿 창에 출력됨

 

 

symbol.description 프로퍼티를 사용하면 설명만 보여주는 것도 가능하다.

let id = Symbol("id");
alert(id.description); // id







 

실무에서의 Symbol

class Counter {
  count = 0;

  add() {
    return this.count++;
  }
  get() {
    return this.count;
  }
}

class BetterCounter extends Counter {
  count = function() { ... }; // conflict !!!!!!!!!!!!!!!!!
  ...
}

 

 

자바스크립트 객체의 내부 필드는 기본적으로 모두 public이다. 이는 누구든 어디에서든 내부 함수 값을 덮어쓸 수 있다는 말이기도 하다.

 

위 코드에서는 그냥 count라는 문자열 key 값을 사용하는데 이를 라이브러리로 외부에 배포했다고 상상해 보자. 그리고 어떤 개발자가 이 Count라는 라이브러리를 받아 상속을 통해 기능을 확장하여 사용하려다가 내부 코드를 잘 모르기 때문에 자연스럽게 변수를 하나 선언하게 되는데 이름을 count라고 지었다면 어떻게 될까.

 

코드를 작성하고 실행하는 과정에서 잘 동작하지 않을 것이다. 로직상 문제가 될 것은 없는데 동작하지 않는 이유는 부모 클래스의 인스턴스 count 변수가 덮어씌워졌기 때문이다.

 

 

심볼을 선언하는 순간 이는 private라고 개발자에게 가독성을 높여줄 뿐만 아니라 인스턴스 중복을 피할 수 있다.

const count = Symbol();

class Counter {
  [count] = 0;
  
  add(){
    this[count] += 1;
    return this;
  }
  
  get(){
    return this[count];
  }
}

const counter = new Counter();
console.log(counter.get()); // 0

counter.add().add().add();
console.log(counter.get()); // 3

 

 


 

 

Symbol.iterator

어떤 객체가 Symbol.iterator를 프로퍼티 key로 사용한 메서드를 가지고 있으면 자바스크립트 엔진은 이 객체가 이터레이션 프로토콜을 따르는 것으로 간주하고 이터레이터로 동작하도록 한다.

 

Symbol.iterator를 프로퍼티 key로 사용하여 메서드를 구현하고 있는 빌트인 객체(빌트인 이터러블)는 아래와 같다. 아래 객체들은 이터레이션 프로토콜을 준수하고 있으며 이터레이터를 반환한다.

 

이터레이터를 반환한다는 뜻은 for of문으로 요소 하나씩 순회가 가능하다는 것을 뜻한다.

 

Array  Array.prototype[Symbol.iterator]

String  String.prototype[Symbol.iterator]

Map  Map.prototype[Symbol.iterator]

Set  Set.prototype[Symbol.iterator]

DOM data structures  NodeList.prototype[Symbol.iterator], HTMLCollection.prototype[Symbol.iterator]

arguments  arguments[Symbol.iterator]

 

// iterable
// Symbol.iterator를 프로퍼티 key로 사용한 메서드를 구현하여야 한다.
// 배열에는 Array.prototype[Symbol.iterator] 메서드가 구현되어 있다.
const iterable = ['a', 'b', 'c'];

// iterator
// 이터러블의 Symbol.iterator를 프로퍼티 key로 사용한 메서드는 이터레이터를 반환한다.
const iterator = iterable[Symbol.iterator]();

// 이터레이터는 순회 가능한 자료 구조인 이터러블의 요소를 탐색하기 위한 포인터로,
// value, done 프로퍼티를 갖는 객체를 반환하는 next() 함수를 메서드로 갖는 객체이다. 
// 이터레이터의 next() 메서드를 통해 이터러블 객체를 순회할 수 있다.
console.log(iterator.next()); // { value: 'a', done: false }
console.log(iterator.next()); // { value: 'b', done: false }
console.log(iterator.next()); // { value: 'c', done: false }
console.log(iterator.next()); // { value: undefined, done: true }

 

 

Symbol 유의점

객체의 key가 Symbol일 경우 for ... in 반복문에서는 배제된다. Object.keys를 사용해도 key가 심볼인 프로퍼티는 배제된다.
숨김 처리 기능을 하는 자료형이기 때문.


심볼형 프로퍼티 숨기기(hiding symbolic property) 원칙
외부 스크립트나 라이브러리는 심볼형 key를 가진 프로퍼티에 접근하지 못함.

 

 

 

 

 

 

 

 

728x90
반응형