자바스크립트와 Well-known Symbol

쏘쏘임·2021년 10월 5일
2
post-thumbnail

0. Intro

작성 계기
Symbol 데이터 타입은 값을 직접 다뤘던 다른 원시타입 객체들과 달라 무척 낯설고 어려웠다. 이 데이터타입의 특성과 목적에 대해 이해하고 친숙해지는 시간을 가져보면서 자바스크립트가 Well-known Symbol을 활용하는 몇가지 방법을 정리를 했다.

심벌 타입은 매우 낯설다. 값을 직접 만들 수도, 눈으로 볼 수도 없기 때문이다. 그냥 할당해주고 싶은데 왜 함수를 호출해야하는 걸까? 키는 뭐고 바인딩해준 변수는 뭐며 description은 또 뭘까? 값은 보이지 않고 그 주변에 관련된 요소들만 잔뜩있는 심벌에 대해 총 정리를 해보자.

1. Symbol 필수 개념

고유하고(unique) 불변하는(immutable) 값

Each possible Symbol value is unique and immutable.
모든 심벌값은 고유하고 불변성을 가진다.
ECMAScript 2021 6.1.5 The Symbol Type

숫자형, 문자열, boolean 등의 기존에 많이 사용하던 원시타입의 값들은 a = 10과 같이 눈으로 보고 조작하며 = 연산자로 직접 값을 할당해줄 수도 있었다. 서랍장 같이 값들을 담아놓은 객체들과는 달리 값에 더 직접적으로 가까이할 수 있는 느낌이다. (심지어 서랍장 그 안의 값들은 다른 공간에서 참조해오지만 서랍장 자체는 직접 가져올 수 있으니 크게 어렵진 않았는데...)

이러한 원시타입의 값들에 대한 느낌이 Symbol 데이터타입을 더 낯설게 하는게 아닐까 싶다. 우리는 Symbol 데이터타입의 '값'을 조작하기는 커녕 직접 볼 수도 없기 때문이다.

생각해보면 당연하다. 심벌의 값에 대한 정의는 '고유하고 불변하는 값' 이다. 그 안에 숫자가 들었던 문자가 들었건 어떤 로직으로 생성되었건 우리는 알 바가 아닌 것이다. 아니, 우리가 알 수 없고 조작할 수 없어야 고유하고 불변할 수 있다 !

따라서 심벌의 생성 방법은 매우 제한적이다.

제한된 생성 방법

심벌은 항상 함수를 호출하여 생성할 수 있다.

다음은 심벌의 생성 방법은 그리 많지 않다. 가장 기본적인 방법은 심벌 함수를 호출하는 것이다.

const mySymbol = Symbol();

console.log(Symbol() === Symbol()); // false

호출을 통해서만 심볼값을 생성할 수 있고 호출할 때마다 고유한 값을 생성한다.

객체가 될 수 없기 때문에 new 연산자로 생성할 수도 없다. 이 또한 빌트인 생성자함수를 이용해 인스턴스를 생성할 수 있는 String, Number 등 타 원시타입들과 다른 점이다.

Description 달기

const mySymbol = Symbol('myRealFirstSymbol');

console.log(mySymbol.description); // myRealFirstSymbol

선택적으로 심볼 생성 함수안에 description, 즉 태그와 같은 설명문을 넣어줄 수 있다. 이는 값을 불러오는 등의 역할과는 전혀 상관없다. 디버깅시 확인할 수 있는 꼬리표 태그 용도다.

Symbol.for / Symbol.keyFor

십벌을 관리하는 전역 심벌 레지스트리에서 사용할 키값을 생성하거나 찾아주는 메서드다.
Symbol.for(키값) 으로 기존에 등록된 키가 없다면 해당 키와 바인딩된 새로운 심벌값을 하나 만들고, 이미 있다면 심벌값을 반환한다.
Symbol.keyFor(식별자) 으로 식별자가 심벌 타입이라면 해당 값의 키를 반환한다.

const mine = Symbol.for('mySymbol');
const alsoMine = Symbol.for('mySymbol');

mine === alsoMine // true

console.log(Symbol.keyFor(mine)); // mySymbol

2. Symbol 특징

Symbol의 래퍼객체

빌트인 객체이자 원시타입인 Symbol도 래퍼객체를 가진다.

// Symbol.prototype.toString 메서드를 호출하고 있다.
console.log(mySymbol.toString('myReallyFirstSymbol')); // Symbol(mySymbol)

타입변환은 boolean만 허용

고유한 값에 대한 타입변환은 참, 거짓으로만 할 수 있다.


!!Symbol() // true

Symbol() + '' // Uncaught TypeError: Cannot convert a Symbol value to a string
+Symbol() // Uncaught TypeError: Cannot convert a Symbol value to a number

3. Symbol 값의 활용

프로퍼티 키로 만들기

원본 객체에 영향을 주지 않기 위해서 겹칠 일도, 반복문을 통해 값이 참조되지도 않는 심볼 값을 키로 준다.
키는 [대괄호]를 감싸주어 사용한다.

const id = Symbol('myId');

const obj = {
	[id] : 'myId',
  	name : 'soso'
}

빌트인 객체나 다른 사람과 공유할 수 있는 객체에 메서드나 프로퍼티를 추가하는 경우 같은 이름의 키로인해 값이 덮어지지 않을 수 있다. 즉, 객체의 확장에 유용하고 은닉이 가능하다.
단, 키 값이 유일하기 때문에 상속받은 하위 프로토타입에서 오버라이딩을 할 수 없다.

Well-known Symbols

자바스크립트가 기본 제공하는 빌트인 심벌값을 Well-known Symbols라고 부른다. 이는 자바스크립트 엔진의 내부 알고리즘에 사용된다.

Symbol.iterator

가장 대표적으로 이터레이터를 반환하고 이터러블의 기준이되게 하는Symbol.iterator가 있다. [Symbol.iterator] 를 프로퍼티 키로 사용한 메서드를 상속받았거나 직접 구현된 객체를 이터러블이라고 한다. 이터러블은 for...of, 스프레드 연산자, 배열 디스트럭쳐링 할당 등에서 이터러블을 이용해 이터러블의 값을 순회하기 전 Symbol.iterator메서드를 호출하여 이터레이터를 반환하고, 이 이터레이터가 이터레이터 리절트 객체 { value, done } 를 반환하여 이터러블 요소를 탐색하기 위한 포인터 역할을 한다.

참고 : 이터레이션 프로토콜과 이터러블이란
ES6는 이터레이션 프로토콜을 적용해 순회가능한 이터러블에 대한 규칙을 통일시켰다. 자료구조(데이터 공급자)마다 각각 다른 방법으로 순회를 돌아야 한다면, 순회시키는 문법(데이터 소비자)은 데이터 공급자에 맞춰 모든 방법을 준비해야 한다. 이는 매우 비효율적이기 때문에 순회 방법을 통일한 것이 이터레이션 프로토콜이며 이터레이션 프로토콜로 순회할 수 있는 자료구조를 이터러블이라고 한다.

Symbol.isConcatSpreadable

Array.prototype.concat()를 사용할 때 배열이 풀어지는지 풀어지지 않는지에 대한 설정이다.

const alpha = ['a', 'b', 'c'];
const numeric = [1, 2, 3];
let alphaNumeric = alpha.concat(numeric);

console.log(alphaNumeric);
// expected output: Array ["a", "b", "c", 1, 2, 3]

numeric[Symbol.isConcatSpreadable] = false;
alphaNumeric = alpha.concat(numeric);

console.log(alphaNumeric);
// expected output: Array ["a", "b", "c", Array [1, 2, 3]]

Symbol.toPrimitive

자바스크립트는 형을 변환을 할 때 다음의 알고리즘을 이용해 어떤 형으로 변환할지 결정한다.
자바스크립트는 객체의 형변환이 필요할 때 가장 먼저 객체에 obj[Symbol.toPrimitive](hint)메서드가 있는지 확인하고 있으면 호출한다. 이를 이용해 해당 키로 원하는 형변환 형태를 지정해서 반환해줄 수 있다. Symbol.toPrimitive 메서드가 없다면 toString 혹은 valueOf 메서드를 이용한다.

(hint 형변환시 목표하는 타입 정도로 생각하면 된다.)

이 외에도...

이 외에도 instanceof 메서드를 사용할 때 호출되는 Symbol.hasInstance, String.prototype.match를 호출할 때 사용되 Symbol.match 등 자바스크립트 내부에서 유일한 값을 찾아내야할 때 내부적으로 사용되는 다양한 Well-known Symbols가 있다.

Conclusion

심벌 타입은 '고유한 값'을 값으로 가지며, 이러한 특성을 가지기 위해 개발자가 직접 값에 접근할 수 없어 엔진이 이를 관리한다. 따라서 값을 생성할 때는 엔진이 제공하는 빌트인 객체의 메서드 Symbol() 혹은 Symbol.key()를 호출해야 한다.

이런 고유한 특성을 이용해 객체 내부에서 고유한 키값을 만들어줄 수도 있고 이터러블 프로토콜, instanceof 메서드 등 자바스크립트 내부에서 다양한 알고리즘을 구현해낼 수 있다.

ECMAScript 2021 6.1.5 The Symbol Type

MDN Symbol

용어집의 심볼 항목(glossary entry for Symbol)

Symbol.isConcatSpreadable

객체를 원시형으로 변환하기

profile
무럭무럭 자라는 주니어 프론트엔드 개발자입니다.

0개의 댓글