My Boundary As Much As I Experienced

자바스크립트의 Symbol 데이터 타입은 무엇인가? 본문

FrontEnd/Javascript(Vanilla)

자바스크립트의 Symbol 데이터 타입은 무엇인가?

Bumang 2024. 2. 29. 14:15

심벌이란?

심벌은 ES6에서 도입된 7번째 데이터 타입으로 변경 불가능한 원시 타입의 값이다. 심벌 값은 다른 값들과 중복되지 않는 유일무이한 값이다. 따라서 주로 이름의 충돌 위험이 없는 유일한 프로퍼티 키를 만들기 위해 사용된다.

심벌 값의 생성

Symbol함수
심벌 값은 Symbol 함수를 호출하여 생성한다. 다른 원시값, 즉 문자열, 숫자, 불리언, undefined, null 타입의 값은 리터럴 표기법을 통해 값을 생성할 수 있지만 심벌 값은 Symbol 함수를 호출해서 생성해야 한다. 이때 생성된 심벌 값은 외부로 노출되지 않아 확인할 수 없으며, 다른 값과 절대 중복되지 않는 유일무이한 값이다.

const mySymbol = Symbol();
console.log(typeof mySymbol); // Symbol

// 심벌 값은 외부로 노출되지 않아 확인할 수 없다.
console.log(Symbol) // Symbol()

언뜻보면 생성자 함수를 쓰는거 같지만 new 연산자를 쓰지 않는다. new 연산자와 함께 호출하는 클래스나 생성자 함수는 객체(인스턴스)를 생성하지만 심벌 값은 변경 불가능한 원시 값이다.

const mySymbol = new Symbol(); // TypeError: Symbol is not a constructor

특징1. 문자열을 선택적으로 받아 디버깅에 쓸 수 있다.

Symbol 함수에는 선택적으로 문자열을 인수로 전달할 수 있다. 이 문자열은 생성된 심벌값에 대한 설명으로 디버깅 용도로만 사용되며, 심벌 값 생성에 어떠한 영향도 주지 않는다. 즉 심벌 값에 대한 설명이 같더라도 생성된 심벌값은 유일무이하다.

const mySymbol1 = Symbol("mySymbol");
const mySymbol2 = Symbol("mySymbol");

console.log(mySymbol1 === mySymbol2); // false

특징2. 심벌 값의 prototype에는 description, toString 메소드가 있다.

string 프로토타입의 toString과는 다르다. 실제로 문자열로 바뀌진 않는다. description을 보여주기만 한다.

const mySymbol = Symbol("mySymbol");

// 심벌도 래퍼 객체를 생성한다.
console.log(mySymbol.description) // mySymbol
console.log(mySymbol.toString()) // Symbol(mySymbol)

특징3. 심벌 값은 문자열이나 숫자 타입으로 변환되지 않는다.

단 암묵적으로 boolean값으로 바꿀 수 있는 방법이 있다. '!!'을 붙이면 된다.
이를 통해 if 문 등에서 존재를 확인할 수 있다.

const mySymbol = Symbol("mySymbol");

// 심벌도 래퍼 객체를 생성한다.
console.log(mySymbol + "") // 안 된다.
console.log(+mySymbol) // 안 된다.

console.log(!!mySymbol) // true

Symbol.for / Symbol.keyFor 메서드

Symbol.for 메서드는 인수로 전달받은 문자열을 키로 사용하여 키와 심벌 값의 쌍들이 저장되어 있는 전역 심벌 레지스트리에서 해당 키와 일치하는 심벌값을 검색한다.

검색에 성공하면 새로운 심벌 값을 생성하지 않고 검색된 심벌값을 반환한다.

// 혹여 "mySymbol"을 description으로 갖는 심벌이 없다면 새로 생성한다.
const S1 = Symbol.for("mySymbol");
const S2 = Symbol.for("mySymbol");

console.log(S1 === S2); // true

Symbol.keyFor를 이용하여 description 값을 추출할 수 있다.

const S1 = Symbol.for("mySymbol");
Symbol.keyFor(S1); // mySymbol

enum 구현

상수를 그냥 객체로 저장할 수 있지만, 이는 위변조의 가능성이 존재한다.

const Diredction = { // 위변조 가능
    UP: 1,
    DOWN: 2,
    LEFT: 3,
    RIGHT: 4
}

const myDirection = Direction.UP;

if (myDirection === Direction.UP) {
    console.log("you are going up")
}

이럴 때 심볼을 사용해서 위변조 못하게 하라고 만들었다.

const Diredction = { // 위변조 가능
    UP: Symbol("up"),
    DOWN: Symbol("down"),
    LEFT: Symbol("left"),
    RIGHT: Symbol("right")
}

const myDirection = Direction.UP;

if (myDirection === Direction.UP) {
    console.log("you are going up")
}

그런데 이제 대세가 된 typeScript는 'as const'를 지정시켜 상수로 쓰게 하거나, 'enum'을 사용 가능하기 때문에,
심볼을 쓰는 일은 별로 없다.

심벌과 프로퍼티 키

마찬가지로 object key를 Symbol을 이용해서 만들면 충돌할 일이 없도록 만들 수 있다.
그리고 해당 프로퍼티는 은닉되어 for in이나 object.Keys, Object.getOwnPropertyNames 등의 메서드를 돌려도 숨겨진다.

const obj = {
    [Symbol.for("mySymbol")]: 1
}

obj.[Symbol.for("mySymbol")] // 1

for (const key in obj){
    console.log(key); // 아무것도 출력되지 않는다.
}

하지만 완벽히 숨길 수 있는 것은 아니다. es6에 도입된 Object.getOwnPropertySymbols 메서드로 접근 가능하다.

심벌과 표준 빌트인 객체 확장

일반적으로 표준 빌트인 객체에 사용자 정의 메서드를 직접 추가하여 확장하는 것은 권장하지 않는다. 표준 빌트인 객체는 읽기 전용으로 사용하는 것이 좋다.
그 이유는 개발자가 이렇게 직접 추가한 빌트인 객체가 미래에 표준 사양으로 추가될 메서드의 이름이 중복될 수 있기 때문이다. 예전에 find()메서드를 직접 빌트인 객체로 추가해서 쓰거나 find를 제공하는 라이브러리를 쓰던 사람들은 ES6에서 find 메소드가 나오자 자신이 추가한 find메소드로 표준 메소드를 덮어써버리는 일이 생겼다.

Array.prototype.sum = function () {
 return this.reduce((acc, cur) => acc + cur, 0); 
};

[1,2].sum(); // 3

하지만 심벌 값으로 프로퍼티 값을 동적 생성하면 다른 프로퍼티 키와 절대 충돌하지 않아 안전하다.

Array.prototype[Symbol.for("sum")] = function () {
 return this.reduce((acc, cur) => acc + cur, 0); 
};

[1,2][Symbol.for("sum")](); // 3

Well-known Symbol

자바스크립트가 기본 제공하는 빌트인 심벌 값을 ECMAScript 사양에서는 Well-known Symbol이라 부른다.
예를 들어, Array, String, Map, Set, TypedArray, arguments, NodeList, HTMLCollection과 같이 for ... of문으로 순회 가능한 빌트인 이터러블은 Well-known Symbol인 Symbol.iterator를 키로 갖는 메소드를 갖는다.

만약 빌트인 이터러블이 아닌 일반 객체를 이터러블 처럼 동작하도록 구현하고 싶다면 이터레이션 프로토콜을 따르면 된다. 즉, ECMAScript 사양에 규정되어 있는 대로 Well-known Symbol인 Symbol.iterator를 키로 갖는 메서드를 객체에 추가하고 이터레이터를 반환하도록 구현하면 그 객체는 이터러블이 된다.

'FrontEnd > Javascript(Vanilla)' 카테고리의 다른 글

script태그의 async/defer의 동작 방식  (0) 2024.02.29
스프레드 문법  (1) 2024.02.29
Strict Mode란?  (0) 2024.02.26
함수와 일급 객체 (+ arguments 객체)  (0) 2024.02.26
생성자 함수와 callable, constructor  (0) 2024.02.22