ES6 이전에는 자바스크립트에 undefined, null, boolean, number, string, object 6가지 타입이 존재했다. 그리고 ES6에서 새로운 일곱번째 타입인 Symbol이 등장했다.
Symbol은 문자열도, 객체도 아닌 새로운 타입이다. 그렇다면, ES6에서 왜 Symbol이 추가되었을까?
Symbol은 객체의 키(key)로 사용하기 위해 만들어졌다.
가령, 우리가 짠 코드의 일부가 다음과 같다고 하자.
if (obj.isRunning) {
callValues(obj);
}
obj.isRunning = false;
평범한 코드 같지만, 사실 상당한 위험을 가지고 있는 코드이다.
isRunning
이 전역변수로 선언되어 있다면 내 코드의 isRunning
과 라이브러리의 isRunning
은 충돌하게 될 것이다. isRunning
이 추가되면서 충돌을 발생시킬 수 있다. isRunning
이라는 객체 메서드가 추가될...수도... 있다! 위의 경우가 발생한다면 유지보수 하기가 엄청나게 어려울 것이다.
이렇게, 다른 사람의 라이브러리를 가져와 작업할 때는 이름 충돌로 인해 생기는 문제가 발생한다.
그렇다고 이 문제를 해결하기 위해 변수 이름을 특이하게 짓는다면.. 직관적이지 않은 변수명으로 인해 유지보수하기 어려울 것이다.
이러한 객체 키 이름 중복으로 인한 충돌 문제를 해결하기 위해 심볼이 태어났다.
자바스크립트는 Symbol()
을 호출하여 새로운 심볼 값을 생성한다. 이 값은 다른 어떤 값들과도 겹치지 않는 고유한 값이다.
let newSymbol = Symbol('new');
우리가 문자열이나 숫자를 객체의 키로 사용하는 것처럼, 심볼 값도 객체의 키로 사용할 수 있다. 또한, 모든 심볼 값은 고유하기 때문에, 다른 객체의 속성과 충돌할 염려도 없다.
Symbol()
의 인자로 들어가는 문자열은 심볼을 구별하는 기능이 아니다. 이 인자는 주석으로, 이 심볼이 무엇을 의미하는지 설명할 수 있도록 하기 위해 존재한다. 디버깅 시에 유용히며, 그 자체로 특별한 기능을 하지 않는다.let oneSymbol = Symbol('one'); let anotherSymbol = Symbol('one'); console.log(oneSymbol); // Symbol('one') console.log(anotherSymbol); // Symbol('one') console.log(oneSymbol === anotherSymbol); // false
앞서 문제가 된 그 코드를 심볼을 이용해 다음과 같이 수정할 수 있다.
let isRunning = Symbol('isRunning');
if (obj[isRunning]) {
callValues(obj);
}
obj[isRunning] = false;
isRunning
은 심볼 값이며, obj[isRunning]
은 심볼값을 키로 가지는 속성값이다. 키의 타입이 심볼 값이라는 점만 빼면 다른 속성들과 똑같다. 다만, isRunning
변수로만 접근할 수 있다. isRunning
에 들어있는 고유한 심볼 값으로 객체의 속성 키를 지정했기 때문이다. 따라서 다른 코드와 충돌할 수 없다.
Symbol()
은 호출할 때마다 새롭고 고유한 심볼을 반환한다.
반면, Symbol.for(string)
은 심볼 레지스트리(symbol registry) 라는 공간을 참조한다. 심볼 레지스트리는 심볼들의 목록으로, 전역 공간에 있다. 이곳에 Symbol.for(string)
이 있다면 해당 심볼을 불러오고, 없으면 심볼 레지스트리에 Symbol.for(string)
을 추가한다.
/* 다음의 Symbol('nittre')들은 서로 다른 고유한 값을 가진 다른 심볼들이다 */
Symbol('nittre');
Symbol('nittre');
Symbol('nittre');
/* 다음의 Symbol.for('nittre')는 모두 같은 심볼이다. */
Symbol.for('nittre');
Symbol.for('nittre');
Symbol.for('nittre');
Symbol.for(string)
을 사용하면 같은 심볼을 코드 내 여러 공간에서 사용할 수 있다는 특징이 있다.
심볼은 다른 문자열과 합칠 수 없다. 심볼을 문자열처럼 사용하기 위해서는 Symbol.toString()
으로 문자열로 변환해야 한다.
let sym = Symbol('sym');
console.log(`hi this is cute ${sym}`); // TypeError
console.log(`hi this is cute ${sym.toString()}`); // hi this is cute Symbol('sym')
심볼은 숫자로는 변환할 수 없다.
let sym = Symbol('sym');
console.log(Number(sym)); // TypeError
객체에 심볼을 키로 사용하면 해당 속성은 for..in에서 제외한다. 따라서 남은 심볼들은 Object.getOwnPropertySymbols()
를 이용해 가져와야 한다.
const object1 = {};
const a = Symbol('a');
const b = Symbol.for('b');
object1[a] = 'localSymbol';
object1[b] = 'globalSymbol';
object1.c = 'normalStringC';
object1.d = 'normalStringD';
for (let el in object1) {
console.log(el)
};
console.log(Object.getOwnPropertySymbols(object1));
// expected Output
// 'normalStringC'
// 'normalStringD'
// [Symbol('a'), Symbol('b')]
JSON.stringify()
도 심볼을 무시한다JSON.stringify()
는 심볼을 키로 사용한 속성을 무시한다.
JSON.stringify({[Symbol("foo")]: "foo"});
// '{}'
자바스크립트 언어 내부의 동작을 나타내는 내장 심볼들이 있다. 목록들은 MDN - Symbol() 잘 알려진 심볼들 참고.