Symbol 심화 — Well-Known Symbol과 메타프로그래밍
Symbol은 ES6에서 추가된 7번째 원시 타입입니다. 단순히 "유일한 값을 만드는 타입"이라고만 알고 있으면 절반만 아는 것입니다. Well-Known Symbol 을 이해하면 자바스크립트 엔진이 객체를 어떻게 다루는지 깊이 있게 파악할 수 있습니다.
Symbol 기본
// 매번 고유한 값 생성
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 — 전역 심볼 레지스트리
// 같은 키로 같은 심볼을 공유
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 — 이터러블 만들기
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 — 타입 변환 제어
객체가 원시값으로 변환될 때의 동작을 정의합니다.
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 커스터마이징
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 결과 제어
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 — 파생 객체의 생성자
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 — 비동기 이터러블
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
// 문자열 메서드의 동작을 커스터마이징
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 활용
// 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.iterator | for...of 순회 |
Symbol.asyncIterator | for await...of 순회 |
Symbol.toPrimitive | 타입 변환 |
Symbol.hasInstance | instanceof |
Symbol.toStringTag | Object.prototype.toString |
Symbol.species | 파생 객체 생성자 |
Symbol.match/replace/search | 문자열 메서드 |
**기억하기 **: Symbol의 핵심은 "고유성"과 "메타프로그래밍"입니다. 일반적인 개발에서는 잘 안 쓰이지만,
Symbol.iterator와Symbol.toPrimitive는 면접에서 자주 나오니 동작 원리를 이해하고 있으면 좋습니다.
댓글 로딩 중...