Modern JS
7번째 데이터 타입 Symbol

7번째 데이터 타입 Symbol

1997년 JS 가 ECMAScript 로 표준화 된 이래로 JS 에는 6개의 타입
문자열, 숫자, 불리언, undefined, null, 객체 타입이 있었다.

Symbol 은 ES6에 도입된 7번째 데이터 타입으로 변경 불가능한 원시 타입이다.
다른 값과 중복되지 않는 유일무이한 값을 가지기 때문에, 충돌하면 안되는 프로퍼티 키 값으로 많이 사용된다.
프로퍼티 키로 사용할 수 있는 값은 빈 문자열, 문자열, 심벌 값이다.

값 만드는 방법

const s = Symbol();
console.log(typeof s); // symbol

언뜻 보면 생성자 함수로 객체를 생성하는 것 같지만 new 연산자와 함께 호출하지 않는다.
Symbol 함수에 선택적으로 문자열을 인수로 전달 가능한데, 이 문자열은 단순한 설명 용도이다.

const s1 = Symbol('설명1') // s1.description
const s2 = Symbol('설명2')
console.log(s1 === s2); // false

객체 처럼 접근하면 암묵적으로 래퍼 객체를 생성해서 사용한다.
암묵적 타입 변환은 불리언 타입만 작동한다. 이를 통해 if 문 등에서 존재 확인가능하다.
console.log(!!symbol) // true

전역 심벌 레지스트리

키와 심벌 값의 쌍들이 저장되어 있는 global symbol registry 가 있다.
Symbol.for 메서드는 인수로 전달받은 문자열을 키로 사용하여 전역 심벌 레지스트리에서 해당 키와 일치하는 심벌 값을 검색한다.

const s1 = Symbol.for('hayashi') // 인수를 키로 갖는 심벌 값 저장
const s2 = Symbol.for('hayashi') // 이미 있기 때문에 심벌 값 반환
console.log(s1 === s2) // true;

Symbol 함수를 호출하여 생성한 심벌 값은 전역 심벌 레지스트리에 등록되어 관리되지 않는다.

const s = Symbol('doo')
Symbol.keyFor('doo') // undefined

예제

상수

왼쪽의 경우에는 상수 값 1,2,3,4 가 변경될 수 있으며, 다른 변수 값과 중복될 수도 있다.

const Direction = {
    UP: 1,
    DOWN: 2,
    LEFT: 3,
    RIGHT: 4
};
const d = Direction.UP;
if (d === Direction.UP) console.log("UP");
const Direction = Object.freeze({
    UP: Symbol('up'),
    DOWN: Symbol('down'),
    LEFT: Symbol('left'),
    RIGHT: Symbol('right')
});
const d = Direction.UP;
if (d === Direction.UP) console.log("UP");

오른쪽의 경우에는 객체의 변경을 방지하기 위한 Object.freeze.
심벌 값으로 중복될 가능성을 제거하였다.

프로퍼티 키

다른 프로터피 키와 절대 충돌하지 않는 유일무이한 키를 만든다.

const obj = {
    [Symbol.for('me')]: 1
}
obj[Symbol.for('me')]; // 1

심벌 값을 프로퍼티 키로 사용하면 for...in 문이나 Object.keys, Object.getOwnPropertyNames 메서드로 찾을 수 없다.
어느 정도 은닉을 가능하게 하지만, ES6 에 추가된 Object.getOwnPropertySymbols 메서도를 통해 찾을 수 있다.

표준 빌트인 객체 확장

Array.property.sum = function () {
    return this.reduce((acc, cur) => acc + cur, 0);
};
[1, 2].sum(); // 3

위와 같이 표준 빌트인 객체에 사용자 정의 메서드를 직접 추가했다고 해보자.
일반적으로 좋지 않은데, 나중에 새롭게 도입된 메서드의 이름이 sum 일 경우에 해당 메서드를 사용자 메서드가 덮어쓰기 때문이다.
하지만 여기서 심벌 값으로 프로퍼티 키를 생성하여, 표준 빌트인 객체를 확장한다면, 그런 문제가 없다.

Array.property[Symbol.for('sum')] = function () {
    return this.reduce((acc, cur) => acc + cur, 0);
};
[1, 2][Symbol.for('sum')](); // 3

Well-known Symbol

JS 에서 기본 제공하는 심벌 값들은 Symbol 함수의 프로퍼티에 할당되어 있다.
그중 하나인 Symbol.iteratorfor...of 문으로 순회 가능한 빌트인 이터러블이 키로 가지고 있다.
Symbol.iterator 메서드를 호출하면 이터레이터를 반환하도록 ECMAScript 사양에 규정되어 있다.
이 규정, 이터레이션 프로토콜을 준수하면, 빌트인 이터러블이 아닌 일반 객체를 이터러블처럼 동작하도록 구현할 수 있다.

const normalObj = {
    [Symbol.iterator]() {
        let cur = 1;
        const max = 5;
        return {
            next() {
                return { value: cur++, done: cur > max + 1 };
            }
        };
    }
};
for (const num of normalObj) {
    console.log(num) // 1 2 3 4 5
}

정리

심벌은 중복되지 않는 상수 값을 생성하는 것은 물론 기존에 작성된 코드에 영향을 주지 않고 새로운 프로퍼티를 추가하기 위해,
즉 하위 호환성을 보장하기 위해 도입되었다.