JavaScript Symbol - 심볼

Symbol

우선 심볼은 원시 값 즉, `primitive` 값을 반환하는 내장 객체를 의미합니다. 그리고 `Symbol` 또한 원시 값으로 사용되고 있습니다. 같은 원시 값으로는 여러가지가 존재를 하는데 `number`, `string`, `boolean` 등이 있습니다. 심볼을 이용하게 되면 이러한 원시 값들을 고유한 하나의 토큰제공하거나 전역으로 사용할 수 있는 원시값을 생성해줍니다. 그렇다면 이러한 심볼은 어떻게 생성하고 사용하는지 알아보도록 하겠습니다. 

 

생성자

심볼은 기본 값(원시 값) 이기 때문에 `new`연산자를 사용하지 않고 생성이 가능합니다. 그렇기 때문에 new 연산자를 통해 생성을 하고자 하면 에러가 발생 합니다. 

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

심볼을 통한 원시 값을 생성하게 되면 독립된 객체가 생성이 되어져 같은 키 값을 가진 심볼이라고 하여도 다르다는 결과가 나오게 됩니다. 

const symbol1 = Symbol("test");
const symbol2 = Symbol("test");
console.log(symbol1 === symbol2); // false

이렇게 기본 생성자를 통해서 생성된 객체는 당연하게도 갈비지 컬렉션의 수집 대상이 되면서 참조가 되지 않게 되면 삭제되게 되어져 있습니다. 

 

그렇다면 삭제되지 않고 재사용이 가능한 심볼을 생성을 하려고 하면 어떻게 해야하는지 보도록 하겠습니다. 심볼에서 제공하는 `Symbol.for()` 메소드는 전역 객체를 생성하는 역할을 담당하고 있습니다. 생성자와 달리 어떤 결과가 나오는지 보도록 하겠습니다. 

const symbol1 = Symbol.for("test");
const symbol2 = Symbol.for("test");
console.log(symbol1 === symbol2); // true

`for()` 함수를 통해 생성된 심볼들은 같은 값으로 평가가 되어 `true`가 반환되고 있습니다. 그렇다면 전역 심볼을 일반 생성자로 호출하면 어떤 결과가 나오는지 보도록 하겠습니다. 

const symbol1 = Symbol.for("test");
const symbol2 = Symbol("test");
console.log(symbol1 === symbol2); // false

전역 심볼을 생성 후에 일반 생성자로 심볼을 생성을 하였지만 같지 않다고 평가 되고 있습니다. 이러한 결과로 보았을 때 심볼 `for()` 메소드로 생성된 전역 객체는 `for()` 로 생성된 심볼끼리 사용할 수 있다든 것을 알 수 있습니다. 

 

추가로, `keyFor()` 함수도 `for()` 함수와 같이 전역 객체를 공유 받아 사용이 가능합니다. 그 이외의 심볼은 사용이 불가능합니다.

Symbol.keyFor(Symbol.for("tokenString")) === "tokenString"; //

이러한 전역 객체의 경우에는 갈비지 컬렉션에서 수집을 하는 대상에서 제외를 하기 때문에 남발하여 사용하는 것은 좋은 방향이 아닐 것입니다. 그렇기 때문에 꼭 필요한 경우에만 전역 객체를 사용하는 것이 좋습니다. 

 

Well-Known Symbols

`Well-Known Symbols`은 `JS`의 내장된 `Symbol` 값들로, 내부 알고리즘과 언어 구조에 사용되고 있습니다. 이러한 심볼들은 프로그래머가 `JS`엔진 기본 동작을 사용자가 정의할 수 있게 확장을 해주고 있습니다. 예시로는 `Symbol.hasInstance`와 `Symbol.Iterator`가 있으며 예시 코드는 아래와 같습니다. 

class Array1 {
  static [Symbol.hasInstance](instance) {
    return Array.isArray(instance);
  }
}

console.log([] instanceof Array1); // true
// -----------------------------------------------
const myIterableObject = {
  [Symbol.iterator]: function* () {
    yield 1;
    yield 2;
    yield 3;
  }
};

for (const value of myIterableObject) {
  console.log(value);  // Outputs: 1, then 2, then 3
}

이렇게 심볼들이 눈에 보이지 않게 작동을 하여 좀 더 개발자가 개발을 쉽게 지원을 하고 있으며, `Iterator`와 같이 직접 만들어서 사용 할 수 있도록 확장성을 높이고 있습니다. 

 

`Well-Known Symbols`는 프로그램의 수명 동안 고정된 수로 존재하며, 개발자나 시스템에 의해 동적으로 생성되지 않습니다. 그렇기에 갈비지 컬렉션의 수집 대상이 되지 않고 또한 삭제되지 않습니다. 

 

Object Symbol

심볼은 기본적으로 오브젝트를 상속을 받기 때문에 `Object` 메소드를 사용 가능합니다. 그리고 오브젝트에 부여된 심볼 값을 확인하기 위해서 `getOwnPropertySymbols()` 함수를 아래와 같이 사용합니다. 

const object1 = {};
const a = Symbol('a');
const b = Symbol.for('b');

object1[a] = 'localSymbol'; // 오브젝트에 "a" : "localSymbol" 할당
object1[b] = 'globalSymbol'; // 오브젝트에 "b" : "globalSymbol" 할당

const objectSymbols = Object.getOwnPropertySymbols(object1); // 오브젝트의 속성 값 가져오기

console.log(objectSymbols.length); // 2
objectSymbols.forEach(symbol => {
    console.log(obj[symbol]);  // 출력: "localSymbol", 그리고 "globalSymbol"
});

여기서 알 수 있듯이 심볼은 키값에 대한 역할을 하고 있으며, 반환되는 키값은 배열로 받아져 `forEach`문으로 추력이 가능합니다. 

 

그렇다면 객체는 어떻게 처리되고 있을까요? 같이 보도록 하겠습니다. 

const customObj = {
  a: "localSymbol",
  b: "globalSymbol",
};

const objNames = Object.getOwnPropertyNames(customObj);

console.log(objNames.length); // 2

objNames.forEach((name) => {
  console.log(customObj[name]); 
});

 위의 코드에서는 `getOwnPropertySymbols()` 메소드가 아니라 `getOwnPropertyNames()`를 사용한 것을 알 수 있는데요 이것은 당연하게도 객체를 생성할 당시에 심볼을 이용한 값 할당 방식이 아니기 때문입니다. 

 

Static Property

`JavaScript`에서 `Symbol`의 정적(static) 프로퍼티는 `Symbol` 생성자에 직접 연결된 프로퍼티들을 나타냅니다. `Symbol` 생성자의 정적 프로퍼티들은 주로 `Well-Known Symbols`를 나타내며, 이들은 `JavaScript `언어의 기본 동작을 사용자 정의하고 확장할 수 있는 데 사용됩니다.

 

Symbol.iterator

객체가 어떻게 for...of 구문에 의해 반복될 것인지를 정의합니다.

const myIterable = {
  [Symbol.iterator]: function* () {
    yield 1;
    yield 2;
    yield 3;
  }
};

for (const value of myIterable) {
  console.log(value);  // Outputs: 1, 2, 3
}

 

Symbol.toStringTag

객체의 사용자 정의 태그를 반환하는 문자열을 정의합니다.

Copy code
class MyObject {
  get [Symbol.toStringTag]() {
    return 'MyObject';
  }
}

const obj = new MyObject();
console.log(obj.toString());  // Outputs: [object MyObject]

 

Symbol.hasInstance

instanceof 연산자의 동작을 사용자 정의합니다.

class MyObject {
  static [Symbol.hasInstance](instance) {
    return typeof instance === 'object';
  }
}

console.log({} instanceof MyObject);  // Outputs: true

 

해당 글에서는 3개의 예시만 보여줬지만은 여러가지 기능들을 더욱 제공하고 있으니 찾고자 하시는 분은 아래 링크에서 확인 하시면 되겠습니다. 

 

Symbol - JavaScript | MDN

Symbol 은 생성자가 symbol 원시 값을 반환하는 내장 객체입니다. symbol 원시 값은 심볼 값, 혹은 짧게 심볼이라고만 부르며 고유함이 보장됩니다. 심볼은 객체에 속성을 추가할 때 고유한 키를 부여

developer.mozilla.org

 

활용방식

그렇다면 이러한 심볼은 어떻게 사용될 수 있는지 보도록 하겠습니다. 

 

고유한 프로퍼티 키

심볼을 사용하면 객체의 프로퍼티 키 충돌을 방지할 수 있습니다. 각 심볼은 고유하므로 다른 어떤 키와도 충돌하지 않습니다.

const uniqueSymbol = Symbol('description');
const obj = {
    [uniqueSymbol]: "This is a unique description"
};

 

정보 은닉과 캡슐화

심볼을 사용하면 객체의 일부 프로퍼티를 "숨길" 수 있어, 이를 통해 라이브러리의 내부 상태를 보호하고 캡슐화할 수 있습니다.

const hiddenSymbol = Symbol('hidden');
const obj = {
    publicProperty: "I am public",
    [hiddenSymbol]: "I am hidden"
};
console.log(Object.keys(obj));  // Output: ['publicProperty']

 

Well-Known Symbols

`JavaScript`에는 `Well-Known Symbols`라고 불리는 미리 정의된 심볼 세트가 있으며, 이러한 심볼들을 사용하여 기본 언어 동작을 사용자 정의할 수 있습니다. 예를 들어, `Symbol.iterator`를 사용하여 객체의 반복 동작을 정의할 수 있습니다.

const obj = {
    data: [1, 2, 3, 4],
    [Symbol.iterator]: function* () {
        let index = 0;
        while(index < this.data.length) {
            yield this.data[index++];
        }
    }
};

for(const value of obj) {
    console.log(value);  // Output: 1, 2, 3, 4
}

'Language > JavaScript' 카테고리의 다른 글

JavaScript Obejct  (0) 2023.11.05
JavaScript Class  (0) 2023.11.05
JavaScript) Closure 그는 무엇인가?  (0) 2023.09.13
JavaScript) Promise 와 예제  (0) 2023.09.13
LocalStorage, SessionStorage  (0) 2023.01.24