본문 바로가기

자스핑

[Week 01] Symbol.. 그는..

 

안녕하세요 웹파트 배영경입니다😙

JavaScript 세미나 시간에 배웠던 내용 중 저는 Symbol에 대해 더 깊이 알아보았습니다!

symbol을 처음 들어봤는데, ES6에서 추가된 내용이라고 해서 더 눈길이 갔습니다.


 

Symbol은 JavaScript의 데이터 타입 중 하나로, 유일하고 고유한 값을 만드는데 사용된다.
객체의 속성 키로 주로 사용되며, 다른 값들과 달리 symbol은 고유성이 보장되는 특징이 있어 충돌 없이 사용 가능하다.

 

우선, 학습했던 부분에 대해 다시 정리를 해보자면

 

Symbol의 생성

  • Symbol 생성: Symbol() 함수를 호출하여 생성하며, 이 함수는 매번 고유한 심볼을 반환한다.
    심볼은 원시 값이지만, 다른 원시 타입과 달리 고유성을 보장하기 때문에 비교 시 동일한 심볼이 아닌 한 무조건 다른 값으로 인식된다.
const sym1 = Symbol();
const sym2 = Symbol();
console.log(sym1 === sym2); // false, 서로 다른 고유한 값

 

  • 심볼의 문자열(description): Symbol()에 설명을 추가할 수 있는데, 이 설명은 디버깅할 때 유용하며 심볼의 고유성에는 영향을 주지 않는다. (즉, 설명은 같아도 다른 심볼이다.)
const sym1 = Symbol("자스핑");
const sym2 = Symbol("자스핑");
console.log(sym1 === sym2); // false, 설명이 같아도 다른 고유한 값
 

심볼의 주요 사용법

  • 객체 속성 키: 심볼은 객체의 속성 키로 사용되며, 특히 고유해야 하는 속성을 만들 때 유용하다. 예를 들어, 외부 라이브러리나 다른 코드와 충돌을 피하고자 할 때 심볼 키를 사용하면 안전하게 고유 속성을 추가할 수 있다.
const uniqueKey = Symbol("key");
const jsObject = {
  [uniqueKey]: "레전드 자스핑",
};
console.log(jsObject[uniqueKey]); // "레전드 자스핑"
 

심볼의 열거 불가 특성 (Non-enumerability)

  • 심볼로 정의된 속성은 기본적으로 열거되지 않는다.
    예를 들어 for...in 루프나 Object.keys()로 객체의 속성을 나열할 때 심볼 속성은 출력되지 않으므로 감춰진 속성처럼 취급된다.
    그러나 Object.getOwnPropertySymbols()로 심볼 속성만 따로 조회할 수 있다.
const symKey = Symbol("hidden");
const obj = {
  visible: "보이는 속성",
  [symKey]: "안보일 예정",
};

for (let key in obj) {
  console.log(key); // 'visible'만 출력, symKey는 출력되지 않음
}

console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(hidden)]

 

심볼이 열거되지 않는 이유는, 이 속성이 충돌 없이 고유하게 존재하면서도 일반적인 속성 조회에는 나타나지 않도록 함으로써 객체의 다른 코드나 라이브러리와 충돌을 피할 수 있게 하기 위함.

 

Symbol의 활용 예시

  • Symbol은 주로 프라이빗 속성처럼 숨기고 싶은 객체의 속성을 관리할 때 사용되며, 여러 개발자가 같은 객체를 확장해야 하는 상황에서도 고유한 속성 추가가 가능해 충돌을 피할 수 있다.
  • 고유한 상수: 특정 값이 재할당되지 않고 고유해야 할 때 심볼을 사용해 안전하게 관리할 수 있다.

ES5 이전에는 상수를 정의할 때 문자열을 많이 사용했다.

예를 들어 요일을 나타내는 상수를 문자열로 지정:

var WEEK_MON = 'Monday';
var WEEK_TUE = 'Tuesday';
var WEEK_WED = 'Wednesday';
var WEEK_THU = 'Thursday';
var WEEK_FRI = 'Friday';
var WEEK_SAT = 'Saturday';

이렇게 문자열 상수를 사용하면, 동일한 이름의 상수를 실수로 중복 정의하거나 변경할 위험이 있다.
예를 들어 WEEK_SUN을 실수로 'Saturday'로 다시 정의하면 중복이나 의도하지 않은 값 덮어쓰기가 발생할 수 있다.

var WEEK_SUN = 'Saturday'; // 실수로 정의한 중복 상수

이럴 경우, 우리가 예상치 못한 오류가 발생할 수 있고, 코드가 복잡해질수록 오류 추적이 어려워진다.

또한, 상수로의 제대로 된 역할을 하지 못하게 된다.

 

 

이 문제를 해결하기 위해 Symbol을 사용해 상수를 정의하면, Symbol이 고유성을 보장하기 때문에 상수 중복이나 덮어쓰기가 불가능해진다.

const WEEK_MON = Symbol('MON');
const WEEK_TUE = Symbol('TUE');
const WEEK_WED = Symbol('WED');
const WEEK_THU = Symbol('THU');
const WEEK_FRI = Symbol('FRI');
const WEEK_SAT = Symbol('SAT');
const WEEK_SUN = Symbol('SUN');

Symbol은 매번 고유한 값을 생성하므로, 이 상수들은 절대 중복되지 않으며, 고유한 상태로 유지된다.

이제 같은 이름을 가진 중복된 값이 만들어질 수 없기 때문에, 예기치 않은 오류가 발생하지 않을 수 있다.

function getDay(day) {
  switch (day) {
    case WEEK_MON:
      return "Monday";
    case WEEK_TUE:
      return "Tuesday";
    case WEEK_WED:
      return "Wednesday";
    case WEEK_THU:
      return "Thursday";
    case WEEK_FRI:
      return "Friday";
    case WEEK_SAT:
      return "Saturday";
    case WEEK_SUN:
      return "Sunday";
    default:
      throw new Error("Unknown day: " + day);
  }
}

 

 


 

Symbol.for와 전역 심볼 레지스트리

Symbol.for()는 자바스크립트의 전역 심볼 레지스트리(Global Symbol Registry)를 사용해 심볼을 관리한다. 이 전역 레지스트리는 애플리케이션 전역에서 공유되는 심볼들을 저장하는 공간이다.

  • Symbol.for(key): 동일한 키로 심볼을 생성하거나, 이미 생성된 심볼을 재사용할 때 사용된다.
    Symbol()과 달리 Symbol.for()는 같은 키를 가진 심볼을 여러 번 호출해도 항상 동일한 심볼을 반환하므로, 애플리케이션 전역에서 공통된 심볼을 사용할 때 유용하다.
  • Symbol.keyFor(sym): Symbol.for()로 생성된 심볼을 다시 조회할 때 사용한다.
    전역 레지스트리에서 해당 심볼의 키를 반환해 준다. Symbol()로 생성한 심볼은 전역 레지스트리에서 관리되지 않으므로 Symbol.keyFor()로 조회할 수 없다.

예시

// 'sharedKey'로 전역 레지스트리에 심볼 등록
const sym1 = Symbol.for("sharedKey");
const sym2 = Symbol.for("sharedKey");

console.log(sym1 === sym2); // true, 동일한 키로 생성된 심볼이므로 같은 값 반환

// 전역 레지스트리에서 키를 통해 심볼 조회
console.log(Symbol.keyFor(sym1)); // "sharedKey"

 

🧐 고유성, 유일성이 Symbol을 쓰는 이유인데, 왜 symbol.for(), 전역 심볼 레지스토리가 필요할까?

일반적으로 Symbol은 객체의 속성 키로 사용하여 충돌 없이 고유성을 보장하는 목적이 크지만, 같은 애플리케이션 내의 여러 모듈이 동일한 심볼을 공유해야 할 때도 있다. 이럴 때 Symbol.for()와 전역 심볼 레지스트리는 충돌을 방지하면서도 재사용할 수 있는 고유한 값을 제공한다.

<충돌 방지와 안전성 보장>
문자열이나 숫자는 코드 전체에서 누군가 동일한 값을 무심코 사용하거나 변경할 가능성이 있다. 특히, 애플리케이션 규모가 크거나 협업하는 경우, 의도하지 않은 중복이나 변경으로 버그가 발생할 수 있다.
Symbol.for()로 만든 심볼은 전역적으로 재사용 가능하지만, 다른 데이터 타입과는 다르게 고유성이 보장되어 다른 코드에서 실수로 사용하거나 중복되는 일이 없도록 안전하게 보호된다.

<특별한 의미 부여>
심볼을 사용하는 것은 이 속성(또는 값)이 의도적으로 다른 값과 구별되며, 심볼이 가지는 유일성을 기반으로 설계되었다는 의미를 부여하는 효과가 있다.

<객체 속성의 은닉성과 보호>
심볼은 객체 속성으로 사용할 때 열거되지 않는 특성을 가지고 있어, 내부 값이나 모듈에서만 접근 가능한 값을 안전하게 관리할 수 있다.

 


 

내장 심볼 (Well-Known Symbols)

자바스크립트가 기본 제공하는 빌트인 심벌값을 Well-known Symbols라고 부른다. 객체의 기본 동작을 사용자 정의하거나, JavaScript의 기본 기능을 커스터마이징할 수 있도록 해 준다.

 

1. Symbol.iterator

 

Symbol.iterator는 객체를 반복 가능한(iterable) 객체로 만드는 심볼로, 이를 정의하면 for...of 루프와 같은 반복문에서 객체를 순회할 수 있다.

  • 반복 가능한 객체(iterable): for...of 루프에서 순회될 수 있는 객체.
    Array, Map, Set과 같은 내장 객체는 기본적으로 Symbol.iterator 메서드를 구현하고 있기 때문에 반복이 가능하지만, 직접 만든 객체에 반복 기능을 추가하려면 Symbol.iterator 메서드를 구현해야 합니다.

구조와 사용법

Symbol.iterator 메서드는 반환값으로 반복자(iterator)를 제공해야 하며, 반복자는 next() 메서드를 가지며, { value, done } 형태의 객체를 반환한다.

  • value: 현재 반복할 값을 나타내고
  • done: 반복이 끝났으면 true, 아니면 false

아래 예제에서는 반복자의 형태를 쉽게 만들기 위해 제너레이터(generator)를 사용한다. 제너레이터 함수는 *를 사용하여 선언하며, 이 함수는 next() 메서드를 자동으로 제공한다. 따라서 아래 코드에서 *[Symbol.iterator]()로 정의된 메서드는 제너레이터를 통해 1, 2, 3을 순차적으로 반환하게 된다.

const myIterable = {
  *[Symbol.iterator]() {
    yield 1;
    yield 2;
    yield 3;
  },
};

// for...of에서 사용할 수 있음
for (const value of myIterable) {
  console.log(value); // 1, 2, 3
}

위 코드에서 *[Symbol.iterator]()제너레이터 함수를 이용해 반복자를 정의한 것이고, yield를 통해 값을 순차적으로 반환하며, for...of가 이를 하나씩 받아 출력하게 된다.

 

2. Symbol.toPrimitive

Symbol.toPrimitive는 객체가 원시 값으로 변환될 때 호출되는 메서드로, 객체가 숫자나 문자열로 변환될 때의 동작을 사용자 정의할 수 있다. 이는 +== 연산자가 사용될 때 특히 유용하다.

 

구조와 사용법

  • Symbol.toPrimitive 메서드는 하나의 인자(hint)를 받으며, 이 hint는 변환이 필요한 타입을 가리킨다. 이 값은 보통 "number", "string", "default" 중 하나로 제공된다.
const obj = {
  [Symbol.toPrimitive](hint) {
    if (hint === "number") return 10; // 숫자 변환 시 10 반환
    return "default"; // 그 외에는 'default' 반환
  },
};

console.log(+obj);        // 10, hint가 "number"로 전달됨
console.log(String(obj)); // "default", hint가 "string"으로 전달됨

+obj는 hint를 "number"로 전달하고, String(obj)는 "string"으로 전달한다. 각각의 힌트에 맞는 값을 반환함으로써 원시 값으로 변환되는 과정을 커스터마이징할 수 있다.

 

3. Symbol.toStringTag

Symbol.toStringTag는 Object.prototype.toString() 메서드로 객체의 타입을 사용자 정의할 수 있는 심볼이다.

객체를 Object.prototype.toString.call()로 호출하면 [object Type] 형식으로 반환되는데, Symbol.toStringTag를 사용하면 이 부분을 원하는 대로 설정할 수 있다.

 

구조와 사용법

const myObj = {
  [Symbol.toStringTag]: "MyCustomObject",
};

console.log(Object.prototype.toString.call(myObj)); // "[object MyCustomObject]"

 

4. Symbol.hasInstance

Symbol.hasInstance는 instanceof 연산자를 커스터마이징할 수 있는 심볼이다.

클래스에 Symbol.hasInstance 메서드를 정의하면, 특정 조건에 따라 instanceof가 true 또는 false를 반환하게 설정할 수 있다.

 

구조와 사용법

class MyClass {
  static [Symbol.hasInstance](instance) {
    return instance === "specialInstance";
  }
}

console.log("specialInstance" instanceof MyClass); // true
console.log("otherInstance" instanceof MyClass);   // false

instanceof의 동작을 클래스나 특정 조건에 따라 커스터마이징할 수 있다.

 

 

 

 

여전히.. 심볼.. 잘 모르겠지만.. 언젠가 적용해보면서 완전히 이해하게 되었으면 좋겠습니다아🫠

 

같이 읽어보면 좋을 것 같은 아티클 입니다!

https://velog.io/@sosoyim/Well-known-Symbol