심볼형 파트 링크 https://ko.javascript.info/symbol
자바스크립트는 객체 프로퍼티 키로 문자형과 심볼형만을 허용한다.
심볼(symbol)은 유일한 식별자(unique identifier)를 만들고 싶을 때 사용한다.
Symbol()을 사용하면 심볼값을 만들 수 있다.
심볼을 만들 때 심볼 이름이라고 불리는 설명을 붙일 수 있다.
<script>
// id는 새로운 심볼이 된다.
let id = Symbol();
// 심볼 id에는 "id"라는 설명(심볼 이름)이 붙는다.
let id = Symbol("id");
</script>
심볼은 유일성이 보장되는 자료형이기 때문에 심볼 이름이 동일한 심볼이 여러 개 있어도 각 심볼값은 다르다.
심볼에 붙는 설명(심볼 이름)은 단순히 이름표 역할만을 한다.
let id1 = Symbol("id");
let id2 = Symbol("id");
alert(id1 == id2); // false
주의 심볼은 문자형으로 자동 형 변환되지 않는다.
let id = Symbol("id");
alert(id); // TypeError: Cannot convert a Symbol value to a string
많은 자료형들이 문자형으로 암시적 형변환이 일어난다. 그래서 alert 함수에도 문자형으로 형 변환 된 많은 값들이 들어갈 수 있는 것이다.
하지만 심볼형은 문자형으로 형변환이 되지 않는다!
따라서 alert 함수에 위 예시 코드와 같이 심볼형 값을 넣으면 에러가 발생한다.
자바스크립트에선 '언어 차원의 보호장치(language guard)로 심볼형이 다른 형으로 형변환 되지 않게 막아주기 때문이다.
따라서 정말정말 심볼형 값을 출력해야 한다면 .toString() 메서드를 사용하면 된다.
let id = Symbol("id");
alert(id.toString()); // 'Symbol(id)'가 출력됨
심볼을 사용하면 외부에서 접근(현재 코드에서 직접 접근은 가능)할 수 없고 값을 덮어쓸 수도 없는 숨김(hidden) 프로퍼티를 만들 수 있다.
숨김 프로퍼티 기능이 중요한 이유는 현재 코드에 있는 객체와 서드파티(제3의 독자적인) 코드에서 가져온 객체의 이름과 프로퍼티가 같을 경우 심볼형으로 프로퍼티 값을 주면 덮어써지지 않아 식별이 가능하다는 것에 있다.
예를들어 서드파티 코드에서 가져온 user라는 객체가 여러 개 있고, 이 user를 사용해야 한다면 아래와 같이 user에 식별자를 붙여줄 수 있다.
<script>
let user = { // 서드파티 코드에서 가져온 객체
name: "John"
};
let id = Symbol("id");
user[id] = '제3 스크립트의 id 값';
alert( user[id] ); // 심볼을 키로 사용해 데이터에 접근할 수 있다.
</script>
위처럼 선언하면 id 값이 심볼이기 때문에 user.id로 다른 값을 넣어도 덮어써지지 않는다. (고유의 값을 지킬 수 있다!)
만약 이런 상황에서 id를 그냥 문자형으로 쓴다면?
<script>
let user = { name: "John" };
// 문자열 "id"를 사용해 식별자를 만듦
user.id = "스크립트 id 값";
// 만약 제3의 스크립트에서 현재 스크립트와 동일하게 문자열 "id"를 이용해 식별자를 만들었다면?
user.id = "제3 스크립트 id 값"
// 값이 덮어 쓰여서 우리가 만든 식별자는 무의미해진다.
</script>
객체 리터럴 {...}을 사용해 객체를 만든 경우 심볼형 프로퍼티 키를 만들 때는 대괄호를 이용해야한다.
<script>
let id = Symbol("id");
let user = {
name: "John",
[id]: 123 // "id": 123은 안됨
};
</script>
"id: 123"이라고 하면, 심볼 id가 아니라 문자열 "id"가 키가 된다.
키가 심볼인 프로퍼티는 for...in 반복문에서 배제된다.
<script>
let id = Symbol("id");
let user = {
name: "John",
age: 30,
[id]: 123
};
for (let key in user) alert(key); // name과 age만 출력되고, 심볼은 출력되지 않는다.
// 심볼로 직접 접근하면 잘 작동한다.
alert( "직접 접근한 값: " + user[id] ); //123출력
</script>
Object.keys(user)에서도 키가 심볼인 프로퍼티는 배제된다. '심볼형 프로퍼티 숨기기(hiding symbolic property)'라 불리는 이런 원칙 덕분에 외부 스크립트나 라이브러리는 심볼형 키를 가진 프로퍼티에 접근하지 못한다.
하지만 Object.assign은 심볼인 프로퍼티도 포함하여 객체 내 모든 프로퍼티를 복사한다. 자바스크립트는 이런 예외적인 개념들이 참 많다.
assign 메서드가 이렇게 동작하는 것은 사용자가 객체를 복사, 병합할 때는 심볼을 포함한 모든 프로퍼티를 전부 복사, 병합할 것이라는 생각에서 이렇게 설계된 것이다.
<script>
let id = Symbol("id");
let user = {
[id]: 123
};
let clone = Object.assign({}, user);
alert( clone[id] ); // 123
</script>
심볼은 심볼 이름이 같아도 모두 별개로 취급되어 같은 값을 갖지 않는다.
하지만 이름이 같은 심볼이 같은 값을 가리키게 하고 싶은 경우도 있다.
전역 심볼 레지스트리(global symbol registry) 는 이런 경우를 위해 만들어졌다. 전역 심볼 레지스트리 안에 심볼을 만들고 해당 심볼에 접근하면, 이름이 같은 경우 항상 동일한 심볼을 반환한다.
레지스트리 안에 있는 심볼을 읽거나 새로운 심볼을 생성할 때는 Symbol.for(key)를 사용하면 된다.
<script>
// 전역 레지스트리에서 심볼을 읽어오기
let id = Symbol.for("id"); // 심볼이 존재하지 않으면 새로운 심볼을 생성한다.
// 동일한 이름을 이용해 심볼을 다시 읽어오기(좀 더 멀리 떨어진 코드에서도 가능)
// 심볼이 저장된 변수(심볼)가 아니라 심볼 이름을 읽어오는 것!!!
// 착각하지 말자!!
let idAgain = Symbol.for("id");
// 두 심볼은 같다!
alert( id === idAgain ); // true
</script>
이런 전역 심볼 레지스트리 안에 있는 심볼을 전역 심볼이라고 부른다.
전역 심볼의 심볼 이름만 얻어오는 메서드는 Symbol.keyFor 이다.
Symbol.keyFor(심볼) 이렇게 사용한다.
<script>
// 이름을 이용해 심볼을 찾음
let sym = Symbol.for("name");
let sym2 = Symbol.for("id");
// 심볼을 이용해 이름을 얻음
alert( Symbol.keyFor(sym) ); // name
alert( Symbol.keyFor(sym2) ); // id
</script>
Symbol.keyFor는 전역 심볼 레지스트리를 뒤져서 해당 심볼의 이름을 얻어낸다. 검색 범위가 전역 심볼 레지스트리이기 때문에 전역 심볼이 아닌 심볼에는 사용할 수 없다. 전역 심볼이 아닌 인자가 넘어오면 Symbol.keyFor는 undefined를 반환한다.
시스템 심볼은 자바스크립트 내부에서 사용되는 심볼을 말한다.
Symbol.*로 접근할 수 있고 시스템 심볼을 활용하면 객체를 미세 조정, 내장 메서드 등의 기본동작을 변경할 수 있다.
잘 알려진 심볼(well-known-symbols)에서 볼 수 있는 시스템 심볼에는 대표적으로
객체가 원시형으로 변환되는 과정을 알기 위해서는 Symbol.toPrimitive에 대해 알아야 한다. 이 내용에 대해서는 곧 다룬다고 한다.
사실 심볼을 완전히 숨기는 방법은 없다.
내장 메서드 Object.getOwnPropertySymbols(obj)를 사용하면 모든 심볼을 볼 수 있고, 메서드 Reflect.ownKeys(obj)는 심볼형 키를 포함한 객체의 모든 키를 반환한다.
그런데 대부분의 라이브러리, 내장 함수 등은 이런 메서드를 사용하지 않습니다.