본문 바로가기
JS

Symbol

by Hyeon_E 2024. 6. 11.

Symobl

Symbol은 생성자 symbol 원시 값을 반환하는 내장 객체

symobl은 ES6 버전의 JavaScript에서 새롭게 추가 7번째 원시 타입

symbol 원시 값은 심볼 값, 혹은 짧게 심볼이라고만 부르며 고유함이 보장

 

일반적으로 심볼 타입은 객체 프로퍼티 키(property key)를 고유하게 설정함으로써 키의 충돌을 방지하기 위해 사용

 

▶ 심볼의 생성 및 특징

심볼은 Symbol 함수를 호출함으로써 생성할 수 있음

이때 생성되는 심볼은 변경이 불가능한 원시 값

 

const sym = Symbol();

console.log(sym);  // Symbol()
console.log(typeof sym);  // 'symbol'

 

Symbol 함수를 호출할 때 인자로 전달하는 문자열 값은 생성될 신볼에 대한 일종의 설명문(Description)으로, 오직 디버깅(소프트웨어에서 소스 코드의 오류 또는 버그를 찾아서 수정하는 과정)의 용도로만 사용

 

const sym = Symbol('foo');

 

여기서 중요한점은 Symbol('foo')는 문자열 'foo'를 심볼로 변환하는 것이 아님

매번 호출마다 새로운 심볼이 생성

 

const sym = Symbol('foo');
const sym2 = Symbol('foo');

sym === sym2  // false
Symbol('foo') === Symbol('foo')  // false

 

Symbol() 함수는 String, Number, Boolean과 같이 래퍼 객체를 생성하는 생성자 함수와는 달리 new 연산자를 이용한 래퍼 객체의 생성이 불가능

 

- 래퍼 객체

JavaScript의 내장 객체로 원시 값(primitive value)을 감싸 객체처럼 메서드와 프로퍼티를 사용할 수 있도록 해줌

 

const sym = new Symbol(); // Symbol is not a constructor

 

new 연산자를 사용하면 TypeError가 발생함

new 연산자를 이용할 수 없다는 것은 곧 Symbol 함수를 생성자로 사용할 수 없음을 의미

 

- number, string, boolean 타입의 경우 new 연산자를 이용한 래퍼 객체의 생성이 가능함 이렇게 생성되는 래퍼 객체는 해당 타입의 원시 값을 저장하고 있고, 유용한 몇몇 메소드들을 가지고 있음

만약 new 연산자를 이용하지 않고 단순히 Number, String, Boolean 함수를 호출하기만 하면 해당 타입의 원시 값이 생성되기만 하고 래퍼 객체는 생성되지 않음. Array 함수의 경우에는 단순히 함수를 호출하기만 하든 new 연산자를 이용하여 생성자로서 호출하든 결과는 같음

 

정말로 Symbol 래퍼 객체를 생성해야 하면 Object 함수를 사용할 수 있음

const sym = Symbol("foo");
typeof sym; // "symbol"
const symObj = Object(sym);
typeof symObj; // "object"

 

▶ 심볼의 활용

프로퍼티 키란 곧 해당 프로퍼티의 값에 접근하고자 할 때 사용하는 이름

JavaScript에서 객체의 프로퍼티 키는 대개 문자열 값

숫자를 키로 사용하는 경우에도 내부적으로 문자열로 변환되며 빈 문자열을 포함하는 모든 문자열로 만들수 있음

 

const obj = {};

obj.propertyKey1 = 'propertyValue1';
obj['propertyKey2'] = 'propertyValue2';

obj[3] = 'propertyValue3';  // obj['3'] = 'propertyValue3'으로 변환

console.log(obj);  // {propertyKey1: 'propertyValue1', propertyKey2: 'propertyValue2', 3: 'propertyValue3'}

 

심볼로 프로퍼티 키 사용

이 경우, 기본적으로 심볼은 고유하기 때문에 심볼을 키로 갖는 프로퍼티는 다른 어떤 프로퍼티와도 충돌하지 않음

 

const obj = {};

const sym1 = Symbol();
const sym2 = Symbol('foo');
const sym3 = Symbol('foo');

obj[sym1] = 'propertyValue1';
obj[sym2] = 'propertyValue2';
obj[sym3] = 'propertyValue3';  // no conflict with sym2

console.log(obj);  // {Symbol(): 'propertyValue1', Symbol(foo): 'propertyValue2', Symbol(foo): 'propertyValue3'}

console.log(obj[sym1]);  // propertyValue1
console.log(obj[sym2]);  // propertyValue2
console.log(obj[sym3]);  // propertyValue3

 

Well-Known Symbol

Symbol() 함수로 심볼 값을 생성할 수 있다는 것은 이것이 함수 객체라는 의미

이 객체 내부에는 다양한 프로퍼티와 메서드가 존재하는데

이들 중 length와 prototype을 제외한 나머지 Well-Known Symbol이라고 부름

Well-known Symbol은 자바스크립트 기본으로 제공하는 표준 빌트인 심볼 값

 

- 표준 빌트인 객체: ECMAScript 사양의 자바스크립트 실행 환경(브라우저/node.js)에서 모두 사용 가능한 공통 객체

 

자바스크립트 엔진은 동작하는 과정에서 객체들에 대해 이 심볼들의 참조를 시도하는데, 만약 참조가 가능하다면 그 객체는 해당 심볼들이 가진 코드 동작이 가능한 것으로 간주
예를 들어 Symbol.iterator를 가진 객체의 경우, 자바스크립트 엔진은 이 심볼을 키로 갖는 메서드가 정의된 객체를 iterable 객체로 인식

 

- literable 객체, literator 객체?

  • literable 객체: 반복 가능한 객체(for...of 등의 문법을 이용하여 각 요소를 반복할 수 있는 객체)를 의미
  • iterator 객체: 해당 iterable 객체에서 각 요소를 반복하기 위해 사용하는 객체를 의미

특정 iterable 객체에 대하여 반복을 수행한다는 것은 그 iterable 객체의 Symbol.iterator 메소드를 호출하여 iterator 객체를 얻고, 그 iterator 객체의 next() 메소드를 호출하여 요소를 하나씩 꺼내는 것을 의미함

iterable 객체를 이용하는 대표적인 JavaScript 문법이 바로 for...of 문법

 

// (1)
for (const e of arr) alert(e);

// (2)
const iterator = arr[Symbol.iterator]();
while (true) {
    const data = iterator.next();
    if (data.done) break;
    else alert(data.value);
}

 

(1)과 (2)는 사실상 거의 동일함

 

Symbol.iterator를 키로 갖는 메소드를 정의해둔 내장 iterable 객체들의 예시

Array.prototype[Symbol.iterator];

String.prototype[Symbol.iterator];

Map.prototype[Symbol.iterator];

Set.prototype[Symbol.iterator];

arguments[Symbol.iterator];

NodeList.prototype[Symbol.iterator];

HTMLCollection.prototype[Symbol.iterator];

 

 

▶ Symbol.for(), Symbol.keyFor() 메소드

Symbol 함수를 호출함으로써 생성하는 심볼들은 키를 가지고 있지 않으며, 전역 심볼 레지스트리에 저장되지도 않음

전역 심볼 레지스트리(Global Symobl Registry)심볼들이 저장되는 전역공간을 의미하며, 여러 모듈들이 하나의 심볼을 공유하기 위한 용도로 존재함

여러 모듈들이 하나의 심볼을 공유하려면 그 심볼이 키를 가지고 있어야 함

그래야 키를 통해 이미 존재하는 심볼을 찾아 재활용 할 수 있기 때문

 

Symbol.for() 메소드

인자로 전달받은 문자열 값을 키로 갖는 심볼을 전역 심볼 레지스트리에서 찾아 반환하고, 탐색에 실패한다면 그 문자열 값을 키로 갖는 심볼을 새로 생성하여 전역 심볼 레지스트리에 저장한 뒤 이를 반환

 

단순히 Symbol 함수를 호출하여 심볼을 생성하는 것에 비해, 생성되는 심볼이 키를 갖고 있으며 전역 심볼 레지스트리에 저장이 된다는 차이점이 있음

 

const sym = Symbol.for('foo');  // Cretate symbol
const sym2 = Symbol.for('foo');  // Reuse symbol

console.log(sym === sym2);  // true

 

Symbol.keyFor() 메소드

인자로 전달받은 심볼전역 심볼 레지스트리에서 찾고, 그 심볼의 키를 반환하고, 탐색에 실패한다면 undefined를 반환

 

const unsharedSym = Symbol('foo');
const sym = Symbol.keyFor(unsharedSym);
console.log(sym);  // undefined

const sharedSym = Symbol.for('foo');
const sym2 = Symbol.keyFor(sharedSym);
console.log(symKey2);  // foo

 

 

▶ for ... in 문법과 JSON.stringify() 메소드에서의 심볼

심볼은 객체의 프로퍼티 키로 사용하지만 이때, 주의해야 할 점이 하나 있음

기본적으로 JavaScript가 제공하는 for ... in 문법에서 키가 심볼인 프로퍼티들은 열거되지 않음

또한 Object.getOwnPropertyNames() 메소드 또한 키가 심볼인 프로퍼티들은 반환하지 않음

 

따라서 심볼 프러퍼티가 객체의 비공개(프로퍼티 은닉) 및 고유한 프로퍼티로 작동할 수 있음

객체의 내부 상태나 비공개 데이터를 숨기고 관리하기 용이

 

const obj = {};

obj[Symbol('a')] = 'a';
obj[Symbol.for('b')] = 'b';
obj['c'] = 'c';
obj.d = 'd';

for (const propertyKey in obj) {
   console.log(propertyKey);  // logs 'c' and 'd'
}

 

만약 키가 심볼인 프로퍼티들의 목록을 확인하고 싶다면 Object.getOwnPropertySymbols() 메소드를 사용

이 메소드는 프로퍼티의 키로 사용되는 심볼들로 이뤄진 배열을 반환

일반적인 객체는 심볼이 키인 프로퍼티가 없기에 빈 배열을 반환

 

const obj = {}
const obj2 = {}

obj[Symbol('a')] = 'a'
obj[Symbol('b')] = 'b'
obj['c'] = 'c'

console.log(Object.getOwnPropertySymbols(obj));  // [Symbol(a), Symbol(a)]
console.log(Object.getOwnPropertySymbols(obj2));  // []


또한, 객체를 JSON으로 만들 때(JSON.stringify() 메소드 호출)도 키가 심볼인 프로퍼티들은 무시

 

const obj = {
    [Symbol('foo')]: 'propertyValue',
    propertyKey2: 'propertyValue2'
};

JSON.stringify(obj);  // {"propertyKey2":"propertyValue2"}

 

 

Reference 

Symbol

[JavaScript] 심볼 (Symbol) 타입 이해하기

[자바스크립트] 심볼 (Symbol) 타입

 

 

 

 

'JS' 카테고리의 다른 글

화살표 함수(Arrow Function)  (1) 2024.06.11
템플릿 리터럴(template literal)  (1) 2024.06.11
JavaScript OOP (new, Prototype chain, Subclassing)  (0) 2024.06.10
Call Stack  (0) 2024.06.10
실행 컨텍스트(Execution Context)  (1) 2024.06.10

댓글