[모던 자바스크립트 튜토리얼] 4.7 심볼형

개발견 배도르만·2023년 3월 26일
0
post-thumbnail

심볼형

자바스크립트 객체에서 프로퍼티 키로 사용할 수 있는 타입은 '문자형'과 '심볼형' 두 가지이다. 일반적으로는 문자형을 사용하지만 심볼형을 프로퍼티 키로 사용하는 경우를 알아보자.

심볼

'심볼(symbol)'은 유일한 식별자(unique identifier)를 만들고 싶을 때 사용한다.

변수의 개념을 떠올리면 쉽다.

Symbol()을 사용하면 심볼값을 만들 수 있다.

// id는 새로운 심볼이 됩니다.
let id = Symbol();
심볼을 만들 때 심볼 이름이라 불리는 설명을 붙일 수도 있습니다. 심볼 이름은 디버깅 시 아주 유용합니다.

// 심볼 id에는 "id"라는 설명이 붙습니다.
let id = Symbol("id");

심볼은 유일성이 보장되는 자료형이기 때문에, 설명이 동일한 심볼을 여러 개 만들어도 각 심볼값은 다르다.

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

문자열과 심볼은 근본이 다르기 때문에 우연히라도 서로의 타입으로 변환돼선 안 되기 때문이다. 자바스크립트에선 '언어 차원의 보호장치(language guard)'를 마련해 심볼형이 다른 형으로 변환되지 않게 막아준다.

심볼을 반드시 출력해줘야 하는 상황이라면 아래와 같이 .toString() 메서드를 명시적으로 호출해주면 된다.

let id = Symbol("id");
alert(id.toString()); // Symbol(id)가 얼럿 창에 출력됨
symbol.description 프로퍼티를 이용하면 설명만 보여주는 것도 가능합니다.

let id = Symbol("id");
alert(id.description); // id

‘숨김’ 프로퍼티

심볼을 이용하면 ‘숨김(hidden)’ 프로퍼티를 만들 수 있다. 숨김 프로퍼티는 외부 코드에서 접근이 불가능하고 값도 덮어쓸 수 없는 프로퍼티이다.

서드파티 코드에서 가지고 온 user라는 객체가 여러 개 있고, user를 이용해 어떤 작업을 해야 하는 상황에서 user에 심볼을 이용한 식별자를 붙여 보자.

let user = { // 서드파티 코드에서 가져온 객체
  name: "John"
};

let id = Symbol("id");

user[id] = 1;

alert( user[id] ); // 심볼을 키로 사용해 데이터에 접근할 수 있습니다.

문자열 "id" 대신 심볼값으로 식별자를 만든 이유는 심볼은 서드파티 코드에서 접근할 수 없기 때문이다. 서드파티 코드의 객체에 새 프로퍼티를 추가할 수 없는데, 심볼을 사용하면 서드파티 코드 '몰래' 객체에 식별자를 부여할 수 있는 것이다.

제3의 스크립트(자바스크립트 라이브러리 등)에서 user를 식별해야 하는 상황을 가정해 보자. user의 원천인 서드파티 코드, 현재 작성 중인 스크립트, 제3의 스크립트가 각자 서로의 코드도 모른 채 user를 식별해야 하는 상황이다.

제3의 스크립트에선 아래와 같이 Symbol("id")을 이용해 전용 식별자를 만들어 사용할 수 있다.

// ...
let id = Symbol("id");

user[id] = "제3 스크립트 id 값";

심볼은 유일성이 보장되므로 우리가 만든 식별자와 제3의 스크립트에서 만든 식별자가 이름이 같더라도 충돌하지 않는다.

만약 심볼 대신 문자열 "id"를 사용해 식별자를 만들었다면 값이 덮어 쓰여 충돌이 발생할 가능성이 있다.

let user = { name: "John" };

// 문자열 "id"를 사용해 식별자를 만들었습니다.
user.id = "스크립트 id 값";

// 만약 제3의 스크립트가 우리 스크립트와 동일하게 문자열 "id"를 이용해 식별자를 만들었다면...

user.id = "제3 스크립트 id 값"
// 의도치 않게 값이 덮어 쓰여서 우리가 만든 식별자는 무의미해집니다.

Symbols in a literal

객체 리터럴 {...}을 사용해 객체를 만든 경우, 대괄호를 사용해 심볼형 키를 만들어야 한다.

예시:

let id = Symbol("id");

let user = {
  name: "John",
  [id]: 123 // "id: 123"이라고 하면, 심볼 id가 아니라 문자열 "id"가 키가 됨
};

심볼은 for…in 에서 배제된다.

키가 심볼인 프로퍼티는 for..in 반복문에서 배제된다.

예시:

let id = Symbol("id");
let user = {
  name: "John",
  age: 30,
  [id]: 123
};

for (let key in user) alert(key); // name과 age만 출력되고, 심볼은 출력되지 않습니다.

// 심볼로 직접 접근하면 잘 작동합니다.
alert( "직접 접근한 값: " + user[id] );

Object.keys(user)에서도 키가 심볼인 프로퍼티는 배제된다.
'심볼형 프로퍼티 숨기기(hiding symbolic property)'라 불리는 이런 원칙 덕분에 외부 스크립트나 라이브러리는 심볼형 키를 가진 프로퍼티에 접근하지 못한다.
(그런데 Object.assign은 키가 심볼인 프로퍼티를 배제하지 않고 객체 내 모든 프로퍼티를 복사한다. 의도적으로 설계된 것)

전역 심볼

정리하자면, 심볼은 이름이 같더라도 모두 별개로 취급된다. 그런데 이름이 같은 심볼이 같은 개체를 가리키길 원하는 경우도 있을 것이다.
전역 심볼 레지스트리(global symbol registry) 는 이런 경우를 위해 만들어졌다. 전역 심볼 레지스트리 안에 심볼을 만들고 해당 심볼에 접근하면, 이름이 같은 경우 항상 동일한 심볼을 반환한다.

레지스트리 안에 있는 심볼을 읽거나, 새로운 심볼을 생성하려면 Symbol.for(key)를 사용하면 된다.

  • 이름이 key인 심볼 반환
  • 조건에 맞는 심볼이 레지스트리 안에 없으면 새로운 심볼 Symbol(key)을 만들고 레지스트리에 저장
// 전역 레지스트리에서 심볼을 읽습니다.
let id = Symbol.for("id"); // 심볼이 존재하지 않으면 새로운 심볼을 만듭니다.

// 동일한 이름을 이용해 심볼을 다시 읽습니다(좀 더 멀리 떨어진 코드에서도 가능합니다).
let idAgain = Symbol.for("id");

// 두 심볼은 같습니다.
alert( id === idAgain ); // true

이러한 심볼은 전역 심볼이라고 부른다. 애플리케이션에서 광범위하게 사용해야 하는 심볼이라면 전역 심볼을 사용하면 된다.

Symbol.keyFor

이름으로 심볼을 찾거나 Symbol.for()과는 반대로 Symbol.keyFor()을 사용하면 심볼의 이름을 얻을 수 있다.

// 이름을 이용해 심볼을 찾음
let sym = Symbol.for("name");
let sym2 = Symbol.for("id");

// 심볼을 이용해 이름을 얻음
alert( Symbol.keyFor(sym) ); // name
alert( Symbol.keyFor(sym2) ); // id

검색 범위가 전역 심볼 레지스트리이기 때문에 전역 심볼이 아닌 심볼에는 사용할 수 없다. 전역 심볼이 아닌 인자가 넘어오면 Symbol.keyFor는 undefined를 반환한다.

전역 심볼이 아닌 모든 심볼은 description 프로퍼티가 있다. 일반 심볼에서 이름을 얻고 싶으면 description 프로퍼티를 사용하면 된다.

let globalSymbol = Symbol.for("name");
let localSymbol = Symbol("name");

alert( Symbol.keyFor(globalSymbol) ); // name, 전역 심볼
alert( Symbol.keyFor(localSymbol) ); // undefined, 전역 심볼이 아님

alert( localSymbol.description ); // name

시스템 심볼

'시스템 심볼(system symbol)'은 자바스크립트 내부에서 사용되는 심볼이다. 객체를 미세 조정할 때 사용한다.

Symbol.hasInstance
Symbol.isConcatSpreadable
Symbol.iterator
Symbol.toPrimitive

등이 있다.

객체가 어떻게 원시형으로 변환되는지 알기 위해선 Symbol.toPrimitive에 대해 알아야 하는데, 자세한 내용은 추후에 다룬다.

시스템 심볼 각각에 대한 내용은 연관되는 자바스크립트 기능을 학습하면서 알아보겠다.

✍️ 요약

  • Symbol은 원시형 데이터로, 유일무이한 식별자를 만드는 데 사용된다.
  • Symbol()을 호출하면 심볼을 만들 수 있다. 설명(이름)은 선택적으로 추가할 수 있다.
  • 심볼은 이름이 같더라도 값이 항상 다르다.
  • 이름이 같을 때 값도 같길 원한다면 전역 레지스트리를 사용해야 한다.
  • Symbol.for(key)는 key라는 이름을 가진 전역 심볼을 반환한다. key라는 이름을 가진 전역 심볼이 없으면 새로운 전역 심볼을 만든다. key가 같다면 Symbol.for는 어디서 호출하든 상관없이 항상 같은 심볼을 반환한다.
  • 키가 심볼인 경우엔 for..in의 대상이 되지 않아서 의도치 않게 프로퍼티가 수정되는 것을 예방할 수 있다.
  • 외부 스크립트나 라이브러리는 심볼 정보를 갖고 있지 않아서 프로퍼티에 직접 접근하는 것도 불가능하다. 심볼형 키를 사용하면 프로퍼티가 우연히라도 사용되거나 덮어씌워 지는 걸 예방할 수 있다.
  • 자바스크립트 내부에서 사용되는 시스템 심볼은 Symbol.*로 접근할 수 있다.
  • 사실 심볼을 완전히 숨길 방법은 없다. 내장 메서드 Object.getOwnPropertySymbols(obj)를 사용하면 모든 심볼을 볼 수 있고, 메서드 Reflect.ownKeys(obj)는 심볼형 키를 포함한 객체의 모든 키를 반환해준다. 그런데 대부분의 라이브러리, 내장 함수 등은 이런 메서드를 사용하지 않는다.
profile
네 발 개발 개

0개의 댓글