책 읽다가 코딩하다 죽을래

자바스크립트 심볼(Symbol)에 대해서 알아보자 본문

코딩/자바스크립트

자바스크립트 심볼(Symbol)에 대해서 알아보자

ABlue 2024. 4. 16. 00:36

자바스크립트 심볼(Symbol)은 ES6에서 도입된 7번째 데이터 타입으로 다음과 같은 특징이 있다.

 

1. 원시값(primitive)이다.

2. 한 번 초기화되면 변경이 불가능하다.

3. 다른 값과도 중복되지 않는 유일무이한 값이다.

 

심볼은 한 번 선언되면 프로그램이 종료될 때까지 다른 값과 중복되지 않는다는 특징이 있기 때문에 ES6에 도입되었다.

심볼은 다른 데이터 타입에 비해 많이 사용되지는 않지만 iterable, generator 등의 내부시스템의 기본적으로 들어가 있는 개념이므로 심볼을 학습할 필요성이 있다.

 

📕 심 생성

 

은 Symbol 함수를 호출하여 생성한다.

 

const symbol = Symbol();

 

은 다른 원시형 데이터 타입과는 달리 리터럴 표기법이 아닌 Symbol 함수를 호출하여 생성한다.

이때 생성된 심 값은 외부로 노출되지 않으며 다른 값과 절대 중복되지 않는 유일무이한 값이다.

 

const symbol = Symbol("foo");

 

함수는 선택적으로 문자열 데이터 타입을 받는 인수를 전달할 수 있다.

이 문자열은 생성된 심 값에 대한 설명이며, 디버깅 용도로만 사용될 뿐 심 값에 대해 어떠한 영향도 주지 않는다.

 

const sym1 = Symbol("foo");
const sym2 = Symbol("foo");
console.log(sym1 === sym2); // false

 

또한 매번 호출할 때마다 다른 심볼 값을 생성하며 동일한 문자열 인수를 전달해도 다른 심볼 값이 나온다.

 

📗 Symbol 메서드

 

1. Symbol.for(key: string)

 

Symbol.for 메서드는 인수로 전달받은 문자열을 키로 사용하여 키와 런타임 범위의 심볼 레지스트리에서 존재하는 심볼을 검색하고, 존재할 경우 이를 반환한다. 존재하지 않는 경우에는 해당 키를 사용해 전역 심볼 레지스트리에 새로운 심볼을 생성한다.

 

// 전역 심볼 레지스트리에 sym1이라는 키로 저장된 심볼 값이 있으면 새로운 심볼 값을 생성한다.
const sym1 = Symbol.for('sym1');
// 그러나 심볼 값이 이미 존재한 경우에는 해당 심볼 값을 반환한다.
const sym2 = Symbol.for('sym1');

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

 

 

2. Symbol.keyFor(value: string)

 

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

const sym = Symbol.for('symbol');
console.log(Symbol.keyFor(sym)); // 'symbol'

 

❗️ Symbol 함수로 생성된 심볼 값은 전역 심볼 레지스트리에 관리되지 않는다.

 

Symbol 함수는 심볼 레지스트리에서 심볼 값을 검색할 수 있는 키를 지정할 수 없으므로, 전역 심볼 레지스트리에 등록되어 관리되지는 않는다.

 

const symbol = Symbol();
Symbol.keyFor(symbol); // undefined

 

 

📘 전역 심볼 레지스트리의 공유 심볼

자바스크립트 엔진이 관리하는 전역 심볼 레지스트리는 사용 가능한 모든 심볼이 저장되어 있다. 레지스트리에 접근할 수 있는 함수로는 Symbol.for, Symbol.keyFor 메서드가 있다. 이 메서드들은 전역 심볼 레지스트리 테이블과 런타임 환경 사이에서 심볼 값을 전해주는 역할을 한다. 전역 심볼 레지스트리는 대부분 자바스크립트 컴파일 인프라에 내장되어 있고, 레지스트리 내용은 자바스크립트 런타임 환경에서는 Symbol.for, Symbol.keyFor 메서드를 사용하지 않고서는 접근이 불가능하다.
전역 심볼 레지스트리는 설명을 위한 상상의 개념이며 JavaScript 엔진 내의 실제 데이터 구조와는 일치하지 않을 수 있습니다.

참조 : https://another-light.tistory.com/105

 

 

📙 심볼의 쓰임새

 

1. Well-Known Symbol

 

자바스크립트가 기본 제공하는 빌트인 심볼 값을 Symbol ECMA 사양에서는 Well-Known Symbol이라고 부르며 다양한 알고 리즈에서 사용한다.
그중 대표적인 예시는 순회 가능한 빌트인 이터러블이다.

Array, String, Map, Set, TypedArray, Arguments, NodeList, HTMLCollection 등과 같이 순회 가능한 빌트인 이터러블은  Well-known Symbol인 Symbol.iterator를 키로 갖는 메서드를 가지며, Symbol.iterator 메서드를 호출하면 이터레이터를 반환하도록 ECMA 사양에 규정되어 있다.

 

만약 빌트인 이터러블인 아닌데 일반 객체를 이터러블처럼 동작하도록 구현하고 싶다면 이터레이션 프로토콜을 따르면 된다. 아래의 예시처럼 ECMA Well-known Symbol 사양에 규정되어 있는 대로 Symbol.iterator를 호출하고 그 안에서 이터레이터를 반환하면 그 객체는 이터러블이 된다.

const fibonacci = {
  [Symbol.iterator]() { // Symbol.iterator 메서드를 호출
    let pre = 0;
    let cur = 1;
    return {
      next() {
        [pre, cur] = [cur, pre + cur];
        return { value: cur }; // 이터레이터를 반환
      },
    };
  },
};

// fibonacci는 Object이므로 빌트인 이터러블이 아니지만 이터레이터 프로토콜을 준수한 객체이므로 for ... of로 순회할 수 있다.
for(const n of fibonacci) {
  if(n > 1000) {
    break;
  }
  console.log(n); // 1  2  3  5  8  13  21  34  55  89  144  233  377  610  987 ...
}

 

 

 

2. 표준 빌트인 객체 확장

 

자신만의 빌트인 객체 메서드를 만들어 확장하고 싶을 때가 있다.

예로 들면 한때 JS에 string.replaceAll() 메서드가 없던 시절 우리는 replaceAll 메서드는 이런 식으로 직접 구현한 다음 String 빌트인 객체의 prototype에 직접 연결하여 사용할 수 있었다.

 

String.prototype.replaceAll = function(org, dest) {
	return this.split(org).join(dest);
}
console.log("123112".replaceAll("1", "3")); // '323332'

 

 

보기에는 이 코드는 함수의 이름에 맞게 정상적으로 동작한다.

하지만 이렇게 String.prototype에 직접적으로 함수를 추가하는 것은 문제가 생길 수 있다.

그 이유는 내가 만든 메서드의 이름과 미래에 ECMA 스펙에 추가될 메서드의 이름과 겹칠 수 있기 때문이다.

(string.replaceAll 메서드는 ES12(2021)에 추가가 되었다.)

 

이럴 경우 자바스크립트 엔진은 ECMA 스펙에 추가된 메서드를 실행하지 않고  자신이 추가한 메서드가 덮어씌워져서 실행이 되는 불상사가 생긴다.

String.prototype.replaceAll = function (org, dest) {
  return '이거는 가짜 replaceAll 메서드야'
};

// ES12에 추가된 replaceAll로 동작하지 않는다.
console.log("123112".replaceAll("1", "3")); // '이거는 가짜 replaceAll 메서드야'

 

 

그래서 prototype에 직접 연결하여 구현하고 싶다면 Symbol을 사용해야 한다.

 

String.prototype[Symbol.for('replaceAll')] = function (org, dest) {
  return '이거는 내가 직접 구현한 replaceAll 메서드야'
};

// 직접 추가한 빌트인 확장 메서드
console.log("123"[Symbol.for('replaceAll')]('1', '3')); // 이거는 내가 직접 구현한 replaceAll 메서드야
// ES12에 추가한 replaceAll 메서드
'123112'.replaceAll('1', '3'); // '323332'

 

이렇게 사용하면 기존의 메서드와도 겹치지 않으며, 미래에 추가될 메서드와도 절대로 중복되지 않는다.

 

 

 

 

✅ 정리

이처럼 심볼은 중복되지 않는 상수 값을 생성하는 것은 물론 기존에 작성된 코드에는 영향을 주지 않고 미래에 추가될 프로퍼티에도 영향을 받지 않기 위해, 즉 하위 호환성을 보장하기 위해 ES6에 도입되었다.

 

 

🔗 Reference

 

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

Symbol을 사용하는 이유는 뭘까 | symbol usage
Symbol - Javascript | MDN