자바스크립트 ES6 Symbol

ES6 Symbol

자바스크립트는 6개의 타입(자료형)을 가지고 있었다.

기본 자료형 (primitive data type)

  • Boolean
  • null
  • undefined
  • Number
  • String

    객체형 (Object type)

  • Object

Symbol은 ES6에서 새롭게 추가된 7번째 타입이다. Symbol은 유일하며 변경 불가능한(immutable) 기본 자료형(primitive) 값이다. Symbol은 주로 충돌하지 않는 객체의 프로퍼티 키(property key)로 사용한다.

- 심볼의 생성

Symbol은 Symbol() 함수로 생성한다. Symbol() 함수는 호출될 때마다 Symbol 값을 생성한다. (객체가 아니라 변경 불가능)

let thisSymbol = Symbol();

console.log(thisSymbol); // Symbol();
console.log(typeof thisSymbol); // sybmol

인자로 스트링값을 넘길수 있다.

let thisSymbol = Symbol('park');

console.log(thisSymbol); // Symbol(park)
console.log(thisSymbol === Symbol('park')); // false  유일한 함수. 변경 불가

- 심볼의 사용

const obj = {};

obj.prop = 'park';
obj[123] = 123; // 123은 문자열로 변환된다.
// obj.123 = 123;  // SyntaxError: Unexpected number
obj['prop' + 123] = false;

console.log(obj); // { '123': 123, prop: 'park', prop123: false }

Symbol 값도 객체의 프로퍼티 키로 사용할 수 있다. Symbol 값은 유일한 값이므로 Symbol 값을 키로 갖는 프로퍼티는 다른 어떠한 프로퍼티와도 충돌하지 않는다.

const obj = {};

const thisSymbol = Symbol('thisSymbol');
obj[mySymbol] = 123;

console.log(obj); // { [Symbol(thisSymbol)]: 123 }
console.log(obj[thisSymbol]); // 123

- 심볼 객체

// 만든 심볼객체를 콘솔 객체를 보게되면
console.dir(this.Symbol);
// 객체에 prototype을 볼수 있다.

Symbol 객체의 프로퍼티 중에 length와 prototype을 제외한 프로퍼티를 Well-Known Symbol이라 부른다.

- Symbol.iterator

Well-Known Symbol은 자바스크립트 엔진에 상수로 존재하며 자바스크립트 엔진은 Well-Known Symbol을 참조하여 일정한 처리를 한다. 예를 들어 어떤 객체가 Symbol.iterator를 프로퍼티 key로 사용한 메소드 가지고 있으면 자바스크립트 엔진은 이 객체가 이터레이션 프로토콜을 따르는 것으로 간주하고 이터레이터로 동작하도록 한다.

Symbol.iterator를 프로퍼티 key로 사용하여 메소드를 구현하고 있는 빌트인 객체(빌트인 이터러블)는 아래와 같다. 아래의 객체들은 이터레이션 프로토콜을 준수하고 있으며 이터러이터를 반환한다.

// 이터러블
// Symbol.iterator를 프로퍼티 key로 사용한 메소드를 구현하여야 한다.
// 배열에는 Array.prototype[Symbol.iterator] 메소드가 구현되어 있다.
const iterable = ['a', 'b', 'c'];

// 이터레이터
// 이터러블의 Symbol.iterator를 프로퍼티 key로 사용한 메소드는 이터레이터를 반환한다.
const iterator = iterable[Symbol.iterator]();

// 이터레이터는 순회 가능한 자료 구조인 이터러블의 요소를 탐색하기 위한 포인터로서 value, done 프로퍼티를 갖는 객체를 반환하는 next() 함수를 메소드로 갖는 객체이다. 이터레이터의 next() 메소드를 통해 이터러블 객체를 순회할 수 있다.
console.log(iterator.next()); // { value: 'a', done: false }
console.log(iterator.next()); // { value: 'b', done: false }
console.log(iterator.next()); // { value: 'c', done: false }
console.log(iterator.next()); // { value: undefined, done: true }

Symbol.for

Symbol.for 메소드는 인자로 전달받은 문자열을 키로 사용하여 Symbol 값들이 저장되어 있는 전역 Symbol 레지스트리에서 해당 키와 일치하는 저장된 Symbol 값을 검색한다. 이때 검색에 성공하면 검색된 Symbol 값을 반환하고, 검색에 실패하면 새로운 Symbol 값을 생성하여 해당 키로 전역 Symbol 레지스트리에 저장한 후, Symbol 값을 반환한다.

// 전역 Symbol 레지스트리에 foo라는 키로 저장된 Symbol이 없으면 새로운 Symbol 생성
const s1 = Symbol.for('foo');
// 전역 Symbol 레지스트리에 foo라는 키로 저장된 Symbol이 있으면 해당 Symbol을 반환
const s2 = Symbol.for('foo');

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

Symbol 함수는 매번 다른 Symbol 값을 생성하는 것에 반해, Symbol.for 메소드는 하나의 Symbol을 생성하여 여러 모듈이 키를 통해 같은 Symbol을 공유할 수 있다.

Symbol.for 메소드를 통해 생성된 Symbol 값은 반드시 키를 갖는다. 이에 반해 Symbol 함수를 통해 생성된 Symbol 값은 키가 없다.

const shareSymbol = Symbol.for('myKey');
const key1 = Symbol.keyFor(shareSymbol);
console.log(key1); // myKey

const unsharedSymbol = Symbol('myKey');
const key2 = Symbol.keyFor(unsharedSymbol);
console.log(key2); // undefined

Sybmol 사용 사례

내가 만든 프로퍼티가 표준 프로퍼티가 되는 경우 불상사가 발생 하며 의도치 않게 작동하게 된다. 물론 드문일이지만 방어적인 코드를 습관해야한다.

Array 객체에 isArray() 라는 메서드를 심볼을 사용하여 회피 할 것이다.

const isArray = Symbol('isArray');
Array[isArray] = arg => (
  (Object.prototype.toString.call(arg) === '[object Array]') ? 'Array' : 'Not Array'
);
// Array.isArray를 지원하는 모던 브라우저(크롬, 파폭의 최신 버전 등등)에서 아래 코드를 확인해보자.
console.dir(Array);
// 사용은 아래와 같이 하면 된다.
const arr = {};
if(Array[isArray](arr) === 'Array') console.log('나는 배열이당!');
else console.log('나는 배열이 아니당!');


// 심볼문자열 가지고 놀기 (디버깅용이라고함..)
const asdf = Symbol('asdf');
const qwer = Symbol.for('qwer');
console.log(asdf); // Symbol(asdf), 문자열이 아닌 Symbol 타입이다.
console.log(String(asdf)); // 'Symbol(asdf)'
console.log(asdf.toString()); // 'Symbol(asdf)'
console.log('' + asdf); // Uncaught TypeError: Cannot convert a Symbol value to a string
console.log(asdf.toString().substr(7).slice(0, -1)); // 'asdf'
console.log(Symbol.keyFor(qwer)); // 'qwer'

이러한 방법으로 심볼을 사용하게 되면 표준메소드에 구애 받지않고 사용가능하다.

Symbol을 이용한 객체 발라내기

const mutateIterable = function mutateIterable(obj) {
    if(typeof obj !== 'object') {
        return obj;
    }
    if(!obj[Symbol.iterator]) {
        obj[Symbol.iterator] = function * () {
            let keys = Object.keys(obj);
            for(let i = 0, length = keys.length; i < length; i++) {
                yield {key: keys[i], value: obj[keys[i]]};
            }
        };
    }
    return obj;


}

function run() {
    let obj = { a : 1, b: 2, c: 3, d: 4};
    obj['testFunc'] = function returnValue(){ return 4910};
    for (let item of mutateIterable(obj)) {
        if(typeof item === 'function') {
            console.log(item());
        } else {
            console.log(item);
        }
    }
}

run();

reference