Symbol은 ES6에서 추가된 7번째 원시 타입입니다. 단순히 "유일한 값을 만드는 타입"이라고만 알고 있으면 절반만 아는 것입니다. Well-Known Symbol 을 이해하면 자바스크립트 엔진이 객체를 어떻게 다루는지 깊이 있게 파악할 수 있습니다.

Symbol 기본

JS
// 매번 고유한 값 생성
const sym1 = Symbol("id");
const sym2 = Symbol("id");
console.log(sym1 === sym2); // false — 설명이 같아도 다른 심볼

// 객체 프로퍼티 키로 사용
const user = {};
const id = Symbol("id");
user[id] = 12345;

console.log(user[id]);        // 12345
console.log(Object.keys(user)); // [] — Symbol 키는 열거되지 않음

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

JS
// 같은 키로 같은 심볼을 공유
const s1 = Symbol.for("shared");
const s2 = Symbol.for("shared");
console.log(s1 === s2); // true

// 심볼에서 키 역조회
console.log(Symbol.keyFor(s1)); // "shared"

// 일반 Symbol은 전역 레지스트리에 없음
const local = Symbol("local");
console.log(Symbol.keyFor(local)); // undefined

Well-Known Symbol이란?

자바스크립트 엔진이 내부적으로 사용하는 사전 정의된 Symbol 입니다. 이 심볼들을 구현하면 객체의 기본 동작을 재정의할 수 있습니다.

Symbol.iterator — 이터러블 만들기

JS
const range = {
  from: 1,
  to: 5,
  [Symbol.iterator]() {
    let current = this.from;
    const last = this.to;
    return {
      next() {
        if (current <= last) {
          return { value: current++, done: false };
        }
        return { done: true };
      },
    };
  },
};

for (const num of range) {
  console.log(num); // 1, 2, 3, 4, 5
}

// 스프레드, 구조 분해도 가능
console.log([...range]); // [1, 2, 3, 4, 5]
const [first, second] = range; // 1, 2

Symbol.toPrimitive — 타입 변환 제어

객체가 원시값으로 변환될 때의 동작을 정의합니다.

JS
const money = {
  amount: 10000,
  currency: "KRW",

  [Symbol.toPrimitive](hint) {
    switch (hint) {
      case "number":
        return this.amount;          // 숫자 컨텍스트
      case "string":
        return `${this.amount}${this.currency}`; // 문자열 컨텍스트
      default:
        return this.amount;          // 기본 (보통 숫자)
    }
  },
};

console.log(+money);           // 10000 (hint: "number")
console.log(`${money}`);       // "10000KRW" (hint: "string")
console.log(money + 5000);     // 15000 (hint: "default")

Symbol.hasInstance — instanceof 커스터마이징

JS
class Even {
  static [Symbol.hasInstance](num) {
    return typeof num === "number" && num % 2 === 0;
  }
}

console.log(2 instanceof Even);  // true
console.log(3 instanceof Even);  // false
console.log(10 instanceof Even); // true

Symbol.toStringTag — toString 결과 제어

JS
class Database {
  get [Symbol.toStringTag]() {
    return "Database";
  }
}

const db = new Database();
console.log(Object.prototype.toString.call(db));
// "[object Database]" — 기본은 "[object Object]"

// 내장 객체들도 이걸 사용
console.log(Object.prototype.toString.call(new Map()));
// "[object Map]"

Symbol.species — 파생 객체의 생성자

JS
class PowerArray extends Array {
  // map, filter 등이 반환하는 배열의 타입을 지정
  static get [Symbol.species]() {
    return Array; // PowerArray 대신 일반 Array 반환
  }
}

const arr = new PowerArray(1, 2, 3);
const mapped = arr.map((x) => x * 2);

console.log(mapped instanceof PowerArray); // false
console.log(mapped instanceof Array);      // true

Symbol.asyncIterator — 비동기 이터러블

JS
const asyncRange = {
  from: 1,
  to: 3,
  [Symbol.asyncIterator]() {
    let current = this.from;
    const last = this.to;
    return {
      async next() {
        // 비동기 작업 시뮬레이션
        await new Promise((resolve) => setTimeout(resolve, 500));
        if (current <= last) {
          return { value: current++, done: false };
        }
        return { done: true };
      },
    };
  },
};

// for await...of로 순회
(async () => {
  for await (const num of asyncRange) {
    console.log(num); // 0.5초 간격으로 1, 2, 3
  }
})();

Symbol.replace, Symbol.search, Symbol.match

JS
// 문자열 메서드의 동작을 커스터마이징
class CaseInsensitiveSearch {
  constructor(value) {
    this.value = value.toLowerCase();
  }

  [Symbol.search](str) {
    return str.toLowerCase().indexOf(this.value);
  }
}

console.log("Hello World".search(new CaseInsensitiveSearch("WORLD")));
// 6

실전에서 Symbol 활용

JS
// 1. 프라이빗 프로퍼티 (완전한 은닉은 아님)
const _password = Symbol("password");

class User {
  constructor(name, password) {
    this.name = name;
    this[_password] = password;
  }

  authenticate(input) {
    return this[_password] === input;
  }
}

const user = new User("정훈", "1234");
console.log(Object.keys(user));              // ["name"] — 보이지 않음
console.log(Object.getOwnPropertySymbols(user)); // [Symbol(password)] — 하지만 접근 가능

// 2. 열거형(Enum) 구현
const Direction = Object.freeze({
  UP: Symbol("UP"),
  DOWN: Symbol("DOWN"),
  LEFT: Symbol("LEFT"),
  RIGHT: Symbol("RIGHT"),
});

// 3. 라이브러리 충돌 방지
const MY_LIB_KEY = Symbol("myLibInternalKey");
element[MY_LIB_KEY] = { initialized: true };
// 다른 라이브러리와 키 충돌 없음

Well-Known Symbol 정리

Symbol용도
Symbol.iteratorfor...of 순회
Symbol.asyncIteratorfor await...of 순회
Symbol.toPrimitive타입 변환
Symbol.hasInstanceinstanceof
Symbol.toStringTagObject.prototype.toString
Symbol.species파생 객체 생성자
Symbol.match/replace/search문자열 메서드

**기억하기 **: Symbol의 핵심은 "고유성"과 "메타프로그래밍"입니다. 일반적인 개발에서는 잘 안 쓰이지만, Symbol.iteratorSymbol.toPrimitive는 면접에서 자주 나오니 동작 원리를 이해하고 있으면 좋습니다.

댓글 로딩 중...