ES6 이전의 JavaScript에서는 6개의 데이터 타입을 제공했다.
원시 타입으로서 Number, String, Boolean, Undefined, null,
그리고 프로퍼티를 가질수있는 참조 타입, 객체 타입이다.
ES6에서의 JavaScript는 변경 불가능한 값을 위해, Symbol이라는 새로운 7번째 데이터 타입을 만들었다.
Symbol의 값은 다른 값과는 중복되지 않는 유일무이한 값이다.
Symbol 데이터 타입이 가지는 특성과, 사용방법에 대해서 알아보자.
기존의 다른 원시타입들은 각각의 리터럴 표기법을 이용해 값을 생성한다. 하지만 Symbol 값을 생성하기 위해서는 반드시 Symbol 함수를 호출하여 생성하는 방식이다.
Symbol을 생성하는 방법은 Symbol() 와, Symbol.for()가 있다.
let num = 1;
let str = "String";
let bool = true;
let undf = undefined;
let nll = null;
let symb = Symbol();
symb = new Symbol(); // TypeError: Symbol is not a constructor
이때, Symbol은 원시값이므로 객체 타입과 같이 인스턴스를 생성할수 없다는것에 유의하자!
추가적으로, Symbol함수를 호출할때 인자로 String을 전달하여, 해당 Symbol값에 대한 설명(description)을 넣을수있다.
const mySymbol = Symbol("myFirstSymbol");
console.log(mySymbol.description) // myFirstSymbol
console.log(mySymbol.toString()) // Symbol(myFirstSymbol)
이때, 해당 Description은 말그대로 설명일뿐, Symbol 값을 의미하지 않는다는 것에 유의하자!
Symbol 값은 암묵적인 문자열이나 숫자타입으로 변환하지 않는다.
( symbol+""
,+symbol
는 에러를 일으킨다)
하지만 예외적으로 불리언 타입으로는 암묵적인 타입변환이 될수 있다.
( if(symbol)
와 같이, if 문 등에서 값의 존재를 확인하는 것이 가능하다 )
Symbol.for()을 통한 Symbol값 생성이 Symbol()을 통한 Symbol 값 생성과 다른 점은, 전역 심벌 레지스트리(Global Symbol Registry)를 이용한다는 점이다.
전역 심벌 레지스트리 (Global Symbol Registry)
문자열 키와 심벌 값의 쌍들이 저장되어있는 레지스트리
Symbol.for("string") 을 호출시, 전역 심벌 레지스트리에서 해당 문자열이 키로 저장되어 있는 심벌 값을 찾은 후, 없다면 새로운 심벌 값을 생성 및 전역 심벌 레지스트리에 등록, 있다면 해당 심벌 값을 반환하도록 되어있다.
그러기 때문에 Symbol()에 동일한 Description 값을 넣은 두 Symbol은 다른 Symbol이 되는 반면,
Symbol.for()은 같은 string값을 넣는다면 같은 Symbol을 가르키게 된다.
const symbol1 = Symbol("mySymbol");
const symbol2 = Symbol("mySymbol");
symbol1 === symbol2 // false
const symbol3 = Symbol.for("mySymbol");
const symbol4 = Symbol.for("mySymbol");
symbol3 === symbol4 // true
반대로, 전역 심벌 레지스트리에 저장된 심벌 값이 아닌, 해당하는 키를 찾으려면 Symbol.keyfor 메소드를 활용하면 된다.
const symbol1 = Symbol.for("mySymbol");
Symbol.keyfor(symbol1) // mySymbol
const symbol2 = Symbol("mySymbol");
Symbol.keyfor(symbol2) // undefined
이때, Symbol()을 통해 생성된 키값(정확히는 이때는 키 값이 아닌 Description 값이다 )은 전역 심벌 레지스트리에 등록되지 않으므로, keyfor 메소드를 통해서도 키 값을 찾을 수 없다.
Symbol 값은 앞서 설명했듯 유일무의한 값이다.
이는 단 하나만의 상수로서도 활용되어질수 있다.
다음과 같은 예시를보자
const User = {
name : "42",
age : "42",
isMan : true,
isSexy : true,
}
const userName = User.name;
const userIsSexy = User.isSexy;
userName === User.age // true
if(userIsSexy === User.isMan){ // true
console.log(userName + "is Sexy!") // 42 is Sexy !
}
변수 userName과 userIsSexy 는 각각 User.name과 User.isSexy에 할당되어있는 원시값들을 참조하고 있다. 그뜻은, 그것과 동일한 원시 값이라면, 꼭 User.name과 User.isSexy가 아니더라도 얼마든지 같은 값으로서 취급되어질 수 있다는 뜻이다.
이와 같은 값들을 유일한 값으로 만들기 위해 Symbol 원시 값을 활용 할수 있다.
const User = {
name : Symbol("42"),
age : Symbol("42"),
isMan : Symbol("true"),
isSexy : Symbol("true")
}
const userName = User.name;
const userIsSexy = User.isSexy;
userName === User.name // true;
userName === User.age // false;
if (userIsSexy === User.isMan) // false
Symbol은 객체의 프로퍼티 키 값으로서도 활용될수 있다.
const obj = {
[Symbol.for("mySymbol")]: 1,
[Symbol("mySymbol")] : 1 // 접근이 불가
};
obj[Symbol.for("mySymbol")] // 1
obj[Symbol("mySymbol")] // undefined
Symbol 값을 프로퍼티 키로 사용하려면 Symbol 값에 대괄호([])를 감싸야한다. 이렇게 생성된 프로퍼티 키는 다른 프로퍼티 키와 절대 충돌하지 않는다.
일반적으로 표준 빌트인 객체를 확장하는 것은 권장하지 않지만, 확장 해야만 한다면, 이와 같이 Symbol 값을 활용하여 프로퍼티를 추가하는 것이 바람직하다. 이렇게 되면 이후 JavaScript가 더 발전되어, 새로운 메서드들이 추가될경우, 그 메서드를 덮어쓸수 있는 상황을 없앨수 있다.
추가적으로, 위 코드와 같이 Symbol()을 이용해 만든 프로퍼티는 접근이 불가하다. 프로퍼티 키로서 만들어진 Symbol 값은 유일무이한 값이기 때문이다. 심벌 값을 프로퍼티 키로 하여 생성된 프로퍼티는 for ... in 문이나 Object.keys, Object.getOwnPropertyNames 메서드로는 찾을 수 없다.
이러한 프로퍼티는 Object.getOwnPropertySymbols 메서드를 사용하면 접근이 가능하다.
const obj = {
[Symbol("mySymbol")] : 1
};
const symbolKey1 = Object.getOwnPropertySymbols(obj)[0];
obj[symbolKey1] // 1
자바스크립트는 Symbol 함수의 프로퍼티로서 기본적으로 제공 되어지는 빌트인 심벌 값들이 있다.
console.dir(Symbol)
을 통해 해당 프로퍼티들을 확인할 수 있으며, 이 심벌 값들을 Well-known Symbol 이라고 부른다. 이들은 자바스크립트 엔진의 내부 알고리즘에서 사용된다.
한가지 예로, Symbol.iterator 키를 가진 객체는 Spread Operator 나 for ...of 문을 통회 순회 가능하다. 그리고 해당 Symbol.iterator를 호출하면, 이터레이터를 반환하도록 되어있다.
이터레이터:
{value:...,done:...}을 반환하는 next 메서드를 가진 값.