[JavaScript] Well-Known Symbols

Narcoker·2023년 6월 13일
0

JavaScript

목록 보기
35/55
post-custom-banner

Well-Known Symbols

특정한 목적을 가진 내장 Symbol 값들을 의미한다.

ECMAScript6 에서 도입된 Symbol은 고유하고 변경 불가능한 데이터 타입으로
주로 객체 속성의 키에서 사용된다.

Well-Known Symbols은 언어 내에서 내부적으로 사용되는 연산을 나타내는데
이를 이용하면 기본적인 언어 동작을 확장할 수 있다.

Well-known Symbols 이 도입되기 이전에는
특정 메소드가 실행되면 그에 해당하는 변경할 수 없는 알고리즘이 동작해서
개발자의 의도대로 동작됐다.

예를 들어 String.prototype.match를 실행하면 @@match 이름의 알고리즘이 실행됐던것이다.

Well-known Symbols이 도입되므로써 바로 알고리즘이 실행되는것이 아니라
Symbols.match가 실행이된다.

만약 해당하는 Well-knonw Symbols이 존재하지 않는다면 바로 정적 알고리즘이 실행된다.

도입 이유
Well-knonw Symbols 를 도입한 이유는
개발자가 임의로 디폴트 알고리즘을 수정할 수 있게 자유도를 부여하기 위함이다. (오버라이딩)

Symbol.toStringTag

객체의 기본 설명을 커스터마이징할 때 사용하는 Well-Known Symbol

toString()으로 인스턴스 타입을 구하면 [object Object] 와 같은 형태를 반환
이것으로 인스턴스 타입을 명확하게 구할 수 없다.

const Sport = funcion(){};
const obj = new Sports();
console.log(obj.toString()); // [object Object] 

Sports.prototype[Symbol.toStringTag] = "농구"; // 인스턴스 타입 지정
console.log(obj.toString()); // [object 농구]

let myObject = { [Symbol.toStringTag]: "MyCustomObject" };
console.log(myObject.toString()); // "[object MyCustomObject]"

Symbol.isConcatSpreadable

객체가 배열의 concat() 메서드에 의해 연결 가능한지, 또는 연결되어야 하는지를 결정하는 데 사용되는 자바스크립트의 Well-Known Symbol

주로 대상(concat의 파라미터)에 설정한다.

배열이 concat 대상일 경우

일반적으로 두 1차원 배열을 concat 하면 배열을 전개하여 연결한다.
[Symbol.isConcatSpreadable]: false 를 설정하면
원본 값 그대로 concat 된다.

const one = [10, 20], two = ["A", "B"];
console.log(one.concat(two)); // [10, 20, "A", "B"]

two[Symbol.isConcatSpreadable] = false;
console.log(one.concat(two)); // [10, 20, ["A", "B"]

Array-like가 concat 대상일 경우

대상에 [Symbol.isConcatSpreadable]: true 를 설정하면
원본 값이 concat 되는게 아니라 value만 concat 된다.

let array = [1, 2, 3];
let obj = {
  length: 2,
  0: "A",
  1: "B"
};

console.log(array.concat(obj));  // [1, 2, 3, [object Obj]

obj[Symbol.isConcatSpreadable] = true;
console.log(array.concat(obj)); // [1, 2, 3, 'A', 'B']

Symbol.species

생성자 함수가 파생된 객체를 생성해야 할 때 사용되는 Well-Known Symbol

배열과 같은 내장 객체의 메서드들은
새로운 인스턴스를 만들 때 해당 인스턴스의 생성자를 사용한다.

이는 배열의 map, filter, slice 등과 같은 메서드를 호출할 때
새로운 배열 인스턴스를 반환한다.

이때 반환될 인스턴스의 타입을 커스터마이징 할 수 있다.

class MyArray extends Array {
  static get [Symbol.species]() { // 빌트인 Array 오브젝트의 @@species 를 오버라이드
    return Array; // 이 클래스의 메서드를 통해 생성된 인스턴스의 생성자를 Array로 설정
  }
}

let myArray = new MyArray(1, 2, 3);
let mapped = myArray.map(x => x * x);

console.log(mapped instanceof MyArray); // false, 원래 인스턴스 타입은 생성자의 Constructor인  MyArray여야한다.
console.log(mapped instanceof Array); // true

Symbol.toPrimitive

오브젝트를 primitive 타입으로 변환하는 Well-known Symbol

Symbol.toPrimitive 메서드는 하나의 문자열 인자를 가진다.
이 문자열은 변환 유형을 나타내며
number, string, default 중 하나입니다.

let obj = {
  value: 42,
  [Symbol.toPrimitive](hint) {
    switch (hint) {
      case 'number':
        return this.value;
      case 'string':
        return `value: ${this.value}`;
      case 'default':
        return this.value.toString();
      default:
        throw new Error('Invalid hint');
    }
  }
};

consoel.log(20 * obj); 840 (Number 변환)
console.log(20 + obj); // 2042 (string 변환), + 시용 시 문자열로 인식
console.log(`${obj}`); // "value: 42" (string 변환)
console.log(obj + ''); // "42" (default 변환), String인지 Number인지 판단하기 어려울 때

기존 방식

const point = {bonus: 100};
console.log(point.valueOf());

const book = {
  toString() { return 70 },
  valueOf() { return 30 } 
}

console.log(book * 20); // 600
const point = {bonus: 100};
console.log(point.valueOf());

const book = {
  toString() { return 70 },
 // valueOf() { return 30 } 가 없다면 toString() 이 호출된다. 
}

console.log(book * 20); // 1400

Symbol.iterator()

Symbol.iterator를 사용하면 iterator를 반환하는데
이때 iterator 내부에 있는 프로퍼티와 메서드(next() 등)을 오버라이딩하여
커스터마이징할 수 있다.

const obj = {
  [Symbol.iterator](){
    return {
      count: 0,
      maxCount: this.maxCount,
     
      next() {
        if (this.count < this.maxCount)
          return {value: this.count++, done: false};
    	
        return {value: undefined, done: true};
      }
    }
  }
}
 
obj.maxCount = 3;
for (const value of obj){
  console.log(value);
};

/*
0
1
2
*/

Symbol.iterator와 제너레이터 함수의 결합

값 공유
const obj = {};
obj[Symbol.iterator] = function* () {
  yield 1;
  yield 2;
  yield 3;
};
console.log([...obj]); // [1,2,3]

yield 처리 공유

const get = function* {
	yield 10;
    yield 20;
}

const genObj = gen();
console.log(genObj.next()); // { value: 10, done: false }

const obj = genObj[Symbol.iterator]();
console.log(obj.next()); // { value: 20, done: false }
반복문 활용
let range = {
  from: 1,
  to: 5,

  *[Symbol.iterator]() { // *[Symbol.iterator]()는 제너레이터 함수입니다.
    for(let value = this.from; value <= this.to; value++) {
      yield value;
    }
  }
};

for (let num of range) {
  console.log(num); // 1, 2, 3, 4, 5
}

Symbol.asyncIterator()

Symbol.asyncIterator()를 호출하면
AsyncIterator 인스턴스를 생성하여 반환한다.

생성한 인스턴스의 next를 호출하면 {value: 값, done:false} 형태로 변환하며
이 값을 Promise.resolve()의 파라미터 값으로 사용한다.

for-await-of 로 반복한다.

async funciton* point(){
	yield 10;
};
const gen = point();
console.log(gen[Symbol.toStringTag]); // AsyncGenerator
console.log(gen[Symbol.asyncIterator]); // function [Symbol.asyncIterator]() { [native code] }
console.log(gen[Symbol.asyncIterator]().next); // function next() { [native code] } 
async funciton* point(){
	yield 10;
	yield 20;
};
const gen = point()
async function show() {
    // AsyncIterator.next() 순차 실행 -> {value: 10, done: false} 반환
    // Promise.resolve(param)의 파라미터 값으로 사용하여 for-wait-of 으로 보내고
    // value 값을 point에 할당
	for await (const point of gen){ 
    	console.log(point);
    };
};
show();
profile
열정, 끈기, 집념의 Frontend Developer
post-custom-banner

0개의 댓글