...
Symbol이란?
1997년 자바스크립트가 ECMAScript로 처음 표준화된 이래로 자바스크립트는 6개의 타입을 가지고 있었습니다.
[원시 타입 (primitive data type)]
- Boolean
- null
- undefined
- Number
- String
[객체 타입 (Object type)]
- Object
심볼(symbol)은 ES6에서 새롭게 추가된 7번째 타입으로 변경 불가능한 원시 타입의 값입니다.
심볼은 주로 이름의 충돌 위험이 없는 유일한 객체의 프로퍼티 키(property key)를 만들기 위해 사용합니다.
Symbol의 생성
심볼은 3가지 방법으로 생성할 수 있습니다.
Symbol()Symbol.for()Symbol.iterator
Symbol()
Symbol은 Symbol() 함수로 생성합니다.
이때 생성된 Symbol은 객체가 아니라 변경 불가능한 원시 타입의 값입니다.
Symbol() 함수는 String, Number, Boolean과 같이 래퍼 객체를 생성하는 생성자 함수와는 달리 new 연산자를 사용하지 않습니다.
let mySymbol = Symbol();
let mySymbol2 = Symbol("something");
// Symbol()에 문자열을 줄수 있는데 별다릇 뜻은 없고 주석 같은 개념이다.
// 디버깅할때 이 심볼이 어느 심볼인지 구분하기 편하게 하기 위한 장치
console.log(mySymbol); // Symbol()
console.log(typeof mySymbol); // symbol
심볼은 유일성이 보장되는 자료형이기 때문에, 설명이 동일한 심볼을 여러 개 만들어도 각 심볼값은 다릅니다.
심볼에 붙이는 설명(심볼 이름)은 어떤 것에도 영향을 주지 않는 이름표 역할만을 합니다.
let id1 = Symbol("id");
let id2 = Symbol("id");
alert(id1 == id2); // false
Symbol.for()
- 전역 심볼
Symbol() 은 고유한 심볼을 반환합니다.
하지만 Symbol.for() 는 전역으로 존재하는 global symbol table 의 목록을 참조합니다.
때문에 Symbol.for(token string) 으로 정의할때, token string 으로 정의 된 심볼이 있다면, 해당 심볼을 반환하게 됩니다.
// 전역 Symbol 레지스트리에 foo라는 키로 저장된 Symbol이 없으면 새로운 Symbol 생성
const s1 = Symbol.for('foo');
// 전역 Symbol 레지스트리에 foo라는 키로 저장된 Symbol이 있으면 해당 Symbol을 반환
const s2 = Symbol.for('foo');
console.log(s1 === s2); // true
Symbol.keyFor()
Symbol.keyFor 은 global symbol table 로부터 존재하는 Symbol의 token string 을 반환합니다.
var token = Symbol.for("tokenString");
console.log(Symbol.keyFor(token) === "tokenString"); // true
Symbol.description
Symbol.keyFor가 전역 심볼의 이름을 반환하면,
이건 일반 심볼 이름을 반환합니다.
const shareSymbol = Symbol.for('myKey');
console.log(Symbol.keyFor(shareSymbol)); // myKey
const unsharedSymbol = Symbol('myKey');
console.log(Symbol.keyFor(unsharedSymbol)); // undefined
console.log(unsharedSymbol.description); // myKey
Symbol 함수는 매번 다른 Symbol 값을 생성하는 것에 반해,
Symbol.for 메소드는 하나의 Symbol을 생성하여 여러 모듈이 키를 통해 같은 Symbol을 공유할 수 있다.
Symbol.for 메소드를 통해 생성된 Symbol 값은 반드시 키를 갖는다.
이에 반해 Symbol 함수를 통해 생성된 Symbol 값은 키가 없다.
Symbol의 사용
객체의 프로퍼티 키는 빈 문자열을 포함하는 모든 문자열로 만들 수 있습니다.
const obj = {};
const v = "name";
obj[v] = 'myProp';
obj[123] = 123; // 123은 문자열로 변환된다.
// obj.123 = 123; // SyntaxError: Unexpected number
obj['prop' + 123] = false;
console.log(obj);
/*
{
name : 'myProp',
'123' : 123,
prop123 : false
}
*/
Symbol 값도 객체의 프로퍼티 키로 사용할 수 있습니다.
Symbol 값은 유일한 값이므로 Symbol 값을 키로 갖는 프로퍼티는 다른 어떠한 프로퍼티와도 충돌하지 않습니다.
const obj = {};
const mySymbol = Symbol('mySymbol');
const mySymbol2 = Symbol('mySymbol');
obj[mySymbol] = 123;
obj[mySymbol2] = 456;
console.log(obj); // { [Symbol(mySymbol)] : 123, Symbol(mySymbol)] : 456}
console.log(obj[mySymbol]); // 123
💡 심볼은 문자형으로 자동 형 변환되지 않습니다.
자바스크립트에선 문자형으로의 암시적 형 변환이 비교적 자유롭게 일어나는 편입니다.
alert 함수가 거의 모든 값을 인자로 받을 수 있는 이유가 이 때문이죠.
그러나 심볼은 예외입니다.
심볼형 값은 다른 자료형으로 암시적 형 변환(자동 형 변환)되지 않습니다.
아래 예시에서 alert는 에러를 발생시킵니다.
let id = Symbol("id");
alert(id); // TypeError: Cannot convert a Symbol value to a string
문자열과 심볼은 근본이 다르기 때문에 우연히라도 서로의 타입으로 변환돼선 안 됩니다.
자바스크립트에선 '언어 차원의 보호장치(language guard)'를 마련해 심볼형이 다른 형으로 변환되지 않게 막아줍니다.
심볼을 반드시 출력해줘야 하는 상황이라면 아래와 같이 .toString() 메서드를 명시적으로 호출해주면 됩니다.
let id = Symbol("id");
alert(id.toString()); // Symbol(id)가 얼럿 창에 출력됨
symbol.description 프로퍼티를 이용하면 설명만 보여주는 것도 가능합니다.
let id = Symbol("id");
alert(id.description); // id
Symbol 실무
class Counter {
count = 0;
add() {
return this.count++;
}
get() {
return this.count;
}
}
class BetterCounter extends Counter {
count = function() { ... }; // conflict !!!!!!!!!!!!!!!!!
...
}
자바스크립트 객체의 내부 필드는 기본적으로 모두 public입니다.
그 말은 누구든 내부 함수, 값을 덮어 쓸 수 있다는 말이죠.
위 코드에선, 그냥 count라는 문자열 키 값을 사용하는데, 이거를 라이브러리로 외부에 배포를 한 번 했다고 상상을 해보세요.
그리고 다른 어떤 프로그래머가 이 Counter 라이브러리를 받아서 상속을 통해 기능을 확장해서 사용하려고 할때, 보통 내부 코드가 어떻게 되어있는지 모르기때문에 그 프로그래머는 자연스럽게 내부 변수를 하나 선언하는데 이름을 count라고 지었다고 상상해 봅시다.
그리고 코드를 작성하고 돌려보는데 동작이 잘 안될 겁니다.
로직상 문제가 될것은 없는데 문제가 되는 이유는, 부모클래스 인스턴스 count변수가 덮어씌워져있기 때문이죠.
따라서, 이러한 문제를 예방 하기 위해, Symbol을 써서 '숨김 처리'를 해주는 것입니다. 심볼을 이용해서 내부 변수를 선언했다면 중복되는 걱정을 할 필요가 없습니다.
심볼을 선언하는 순간 이건 private 필드라고 프로그래머에게 가독성을 높여줄 뿐만 아니라, 인스턴스 중복을 피할 수 있습니다.
const count = Symbol();
class Counter {
[count] = 0;
add(){
this[count] += 1;
return this;
}
get(){
return this[count];
}
}
const counter = new Counter();
console.log(counter.get()); // 0
counter.add().add().add();
console.log(counter.get()); // 3
Symbol.iterator
어떤 객체가 Symbol.iterator를 프로퍼티 key로 사용한 메소드 가지고 있으면 자바스크립트 엔진은 이 객체가 이터레이션 프로토콜을 따르는 것으로 간주하고 이터레이터로 동작하도록 합니다.
Symbol.iterator를 프로퍼티 key로 사용하여 메소드를 구현하고 있는 빌트인 객체(빌트인 이터러블)는 아래와 같습니다. 아래의 객체들은 이터레이션 프로토콜을 준수하고 있으며 이터레이터를 반환합니다.
이터레이터를 반환한다는 뜻은, for of문으로 요소 하나씩 순회가 가능하다는 말을 뜻합니다.
Array
Array.prototype[Symbol.iterator]
String
String.prototype[Symbol.iterator]
Map
Map.prototype[Symbol.iterator]
Set
Set.prototype[Symbol.iterator]
DOM data structures
NodeList.prototype[Symbol.iterator] HTMLCollection.prototype[Symbol.iterator]
arguments
arguments[Symbol.iterator]
// 이터러블
// 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 유의점
객체의 key가 Symbol일 경우, for..in 반복문에서는 배제된다.
Object.keys 를 사용해도 키가 심볼인 프로퍼티는 배제된다.
당연히 숨김 처리 기능 하는 자료형 이니까
'심볼평 프로퍼티 숨기기(hiding symbolic property) 원칙'
외부 스크립트나 라이브러리는 Symbol형 키를 가진 프로퍼티에 접근 하지 못함
Object.assign은 예외?
Symbol 키를 가진 프로퍼티도 복사한다.
# Reference
https://poiemaweb.com/es6-symbol
https://ko.javascript.info/symbol
https://pks2974.medium.com/javascript%EC%99%80-%EC%8B%AC%EB%B3%BC-symbol-bbdf3251aa28
이 글이 좋으셨다면 구독 & 좋아요
여러분의 구독과 좋아요는
저자에게 큰 힘이 됩니다.