[자바스크립트 ES6+ 기본]19. Symbol Property

Speedwell🍀·2022년 2월 4일
0

Well-Known Symbols

  • ES2019 스펙에서 @@iterator 형태를 볼 수 있음

  • @@는

    • Well-Known Symbol을 나타내는 기호
    • @@match와 Symbol.match가 같음
    • 스펙에서는 @@match 형태를 사용하고
    • 개발자는 Symbol.match 형태를 사용
  • ES2019 기준: 12개 Well-Known Symbol

  • Well-Known Symbol이란?

    • 스펙에서 알고리즘에 이름을 부여하고
    • 이름을 참조하기 위한 빌트인 Symbol 값
  • 개발자 코드 우선 실행

    • match()를 실행하면 디폴트로 @@match를 실행
    • 소스 코드에 Symbol.match를 작성하면
    • @@match가 실행되지 않고 Symbol.match가 실행됨
  • 개발자 코드로 디폴트 기능을 오버라이딩할 수 있음


  • 강좌에서 다루는 것

    • isConcatSpreadable, iterator, match
    • species, toPrimitive, toStringTag
  • 강좌에서 다루지 않는 것

    • asyncIterator, hasInstance(instanceof 처리)
    • replace, search, split는 match와 비슷
    • unscopables는 "use strict"에서 with 문을 사용할 수 없음

Symbol.toStringTag

  • Object.prototype.toString()의 확장

  • toString()으로 인스턴스 타입을 구하면

    • [object Object] 형태로 반환
    • 인스턴스 타입을 명확하게 구할 수 없음
const Book = function(){};
const obj = new Book();
log(obj.toString());
log({}.toString());

// [object Object]
// [object Object]
  • Symbol.toStringTag로 구분 가능
    • [object Object]에서 두 번째에 표시될 문자열을 작성
    • 예: "ABC" 지정, [object "ABC"]로 반환
  • prototype에 연결하여 작성

prototype에 연결하여 작성

const Sports = function(){};
const obj = new Sports();
log(obj.toString());

Sports.prototype[Symbol.toStringTag] = "농구";
log(obj.toString());

// [object Object]
// [object 농구]
  1. 첫 번째의 obj.toString()을 실행하면

    • 인스턴스 타입을 반환하며
    • [object Object]가 반환됨
    • function으로 만들었는데 Object가 반환됨
  2. Sports.prototype[Symbol.toStringTag] = "농구";

    • prototype에 Symbol.toStringTag를 연결하고
    • [object Object]에서 두 번째의 Object에 표시될 문자를 "농구"로 작성함
    • 표시될 문자를 임의로 작성할 수 있음
    • function마다 지정할 수 있으므로 자세하게 구분하여 작성할 수 있음
  3. 두 번째의 obj.toString()을 호출하면

    • [object 농구] 출력
    • 즉, Symbol.toStringTag에 작성한 문자가 출력됨

Symbol.isConcatSpreadable

  • Array.prototype.concat()은
    • 배열의 엘리먼트를 전개하여 반환
const one = [10, 20], two = ["A", "B"];
const show = () => {
  log(one.concat(two));
};
show();
two[Symbol.isConcatSpreadable] = true;
show();
two[Symbol.isConcatSpreadable] = false;
show();

// [10, 20, A, B]
// [10, 20, A, B]
// [10, 20, [A, B]]
  • [Symbol.isConcatSpreadable] = true
    • one 배열 끝에 two 배열의 엘리먼트를 하나씩 연결
  • [Symbol.isConcatSpreadable] = false

    • 전개하지 않고 two 배열 자체를 연결
  • Array-like 전개

const one = [10, 20];
const like = {0: "A", 1: "B", length: 2};
const show = () => {
  log(one.concat(like));
};
show();
like[Symbol.isConcatSpreadable] = true;
show();
like[Symbol.isConcatSpreadable] = false;
show();

// [10, 20, {0: A, 1: B, length: 2}]
// [10, 20, A, B]
// [10, 20, {0: A, 1: B, length: 2}]

Symbol.species

  • Symbol.species는 constructor를 반환

    • constructor를 실행하면 인스턴스를 생성하여 반환하므로
    • 결국, 인스턴스를 반환
  • Symbol.species를 오버라이드하면

    • 다른 인스턴스를 반환할 수 있다는 의미
  • 우선 Symbol.species와 관련된 개념 살펴보기

    • 메소드를 실행한 후의 결과 형태
    • Symbol.species 기능

메소드를 실행한 후의 결과 형태

const obj = [1, 2, 3];
/*
1. [1, 2, 3]으로 Array 오브젝트를 생성하여 obj에 할당

2. 오른쪽의 obj를 펼쳐서 obj 구조를 보면 prototype은 없고 __proto__만 있으므로

3. obj는 빌트인 Array 오브젝트가 아니라 Array.prototype에 연결된 메소드로 생성한 인스턴스

4. 다만, new 연산자를 사용하지 않았으므로 강좌에서 인스턴스라고 하지 않고 오브젝트라고 한 것
*/
const one = obj.slice(1, 3);

/*
1. 위 코드를 실행한 후의 one과 obj 구조는 차이가 없으며 값 [2, 3]만 다름

2. 이것은 인스턴스에 있는 메소드를 호출하면 메소드 실행 결과값을 반환하지 않고

3. 결과값이 설정된 인스턴스를 반환하기 때문
*/
const two = one.slice(1, 2);

/*
1. 바로 앞에서 반환된 one으로 메소드를 호출할 수 있는 것은 one이 인스턴스이기 때문

2. 또한 slice(1, 2)를 실행하면 결과 값이 설정된 인스턴스를 반환
*/
  1. Array 인스턴스의 메소드를 호출하면 값을 반환하는 것이 아니라

  2. 반환할 Array 인스턴스를 생성하고, 메소드에서 구한 값을 반환할 Array 인스턴스에 설정하여, Array 인스턴스를 반환


Symbol.species 기능

class Sports extends Array {};
const obj = new Sports(10, 20, 30);
const one = obj.slice(1, 2);
log(one);

// [20]
  • class Sports extends Array{}

    • 빌트인 Array 오브젝트를 상속(확장, 연결) 받음
  • const obj = new Sports(10, 20, 30);

    • 인스턴스를 생성
  • const one = obj.slice(1, 2);

    • obj 인스턴스의 slice()를 호출하면
    • slice() 처리 결과를 인스턴스에 설정하여 인스턴스 반환
  • 이렇게 인스턴스의 메소드를 호출했을 때, 인스턴스를 반환하도록 하는 것이 Symbol.species 기능


Symbol.species 오버라이드

  • Symbol.species는
    • static 악세서 프로퍼티이며
    • getter만 있고 setter는 없음
class Sports extends Array {
  static get [Symbol.species](){
    return Array;
  }
};
const obj = new Sports(10, 20);
  • Symbol.species를 사용할 수 있는 빌트인 오브젝트

    • Array, Map, Set, RegExp
    • Promise, ArrayBuffer, TypedArray
  • 빌트인 오브젝트를 상속받은 class에

    • Symbol.species를 작성하면 빌트인 오브젝트의 @@species가 오버라이드됨
  • 인스턴스 바꾸기


인스턴스 바꾸기

class Sports extends Array {
  static get [Symbol.species](){
    return Array;
  }
};
const one = new Sports(10, 20, 30);
log(one instanceof Sports);

const two = one.slice(1, 2);
log(two instanceof Array);
log(two instanceof Sports);

// true
// true
// false
  • class Sports extends Array{}

    • 빌트인 Array 오브젝트를 상속받음
  • static get [Symbol.species](){
    return Array;
    }

    • 빌트인 Array 오브젝트의 @@species를 오버라이드
  • const one = new Sports(10, 20, 30);

    • 인스턴스를 생성
    • 파라미터 값이 인스턴스에 설정됨
  • one intanceof Sports

    • Sports로 one을 만들었으므로 true 출력
  • const two = one.slice(1, 2);

    • Array 오브젝트를 상속받았으므로 one 인스턴스로 slice()를 호출할 수 있음
    • slice() 대상은 인스턴스에 설정된 [10, 20, 30]
    • 인스턴스를 반환하며 반환되는 인스턴스에 slice()를 결과를 설정
  • Symbol.species()로 오버라이드했으므로

    • static get [Symbol.species](){}가 호출됨
    • 호출에 사용한 one 인스턴스 형태를 반환하지 않고 Array 인스턴스를 반환
    • 이처럼 Symbol.species()로 반환할 인스턴스를 변경할 수 있음
  • two instanceof Array

    • two 인스턴스에는 Array 인스턴스가 할당되어 있으며
    • Array 오브젝트로 만들었으므로 true 출력
  • two instanceof Sports

    -Sports가 아니라 Array 오브젝트로 two 인스턴스를 만들었으므로 false 출력


Symbol.toPrimitive

  • 오브젝트를 대응하는 Primitive 값으로 변환

  • 대응, 기대하는 타입

    • number, string, default
  • 오브젝트를 문자열에 대응

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

const book = {
  toString() {
    return "책"
  }
};
log(`${book}`);

// [object Object]
// 책
  • 오브젝트를 숫자에 대응
const point = {bonus: 100};
log(point.valueOf());

const book = {
  toString() { return 70 },
  valueOf() { return 30 }
};
log(book + 20);

// {bonus: 100}
// 600
  • Symbol.toPrimitive() 사용

Symbol.toPrimitive() 사용

const obj = {
  [Symbol.toPrimitive](hint){
    return hint === "number" ? 30 : hint === "string" ? "책" : "default";
  }
};
log(20 * obj);
log(`${obj}` + 100);
log(obj + 50);
log("default" == obj);

// 600
// 책100
// default500
// true
  • 20 * obj

    • 20을 곱하는 숫자 연산으로 처리
    • toPrimitive(hint)의 hint에 엔진이 "number"를 설정
    • 30을 반환하여 20 * 30 = 600을 출력
  • ${obj} + 100

    • hint에 "string"이 설정됨
  • obj + 50

    • hint에 "default"이 설정됨
  • "default" == obj

    • == 비교는 hint에 "default"가 설정됨

Symbol.iterator

  • @@iterator가 있는 빌트인 오브젝트

    • String, Array, Map, Set, TypedArray
  • 빌트인 Object에는 @@iterator가 없지만

    • 개발자 코드로 작성할 수 있음
  • 이 절에서 String, Array, Object를 다루고

    • Map, Set은 관련된 곳에서 다룸
    • TypedArray는 ES6+ 심화 과정에서 다룸

Array.prototype[@@iterator]

  • Array 오브젝트의 [Symbol.iterator]()를 호출하면
    • 이터레이터 오브젝트 반환
    • next()로 배열 엘리먼트 값을 하나씩 구할 수 있음
const list = [10, 20];
const obj = list[Symbol.iterator]();
log(obj.next());
log(obj.next());
log(obj.next());

// {value: 10, done: false}
// {value: 20, done: false}
// {value: undefined, done: true}

Object 이터레이션

  • 빌트인 Object에는 Symbol.iterator가 없음
    • Symbol.iterator가 반복을 처리하므로
    • Object에 Symbol.iterator를 작성하면 반복할 수 있음
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 = 2;
for (const value of obj){
  log(value);
};

// 0
// 1
  • 엔진이 for-of 문을 시작하면

    • 먼저 obj에서 [Symbol.iterator] 검색
    • 이를 위해 obj에 [Symbol.iterator] 작성
  • for(const result of obj)를 처음 실행할 때

    • obj의 [Symbol.iterator]()가 호출되며 return{} 문을 수행
    • obj.maxCount = 2;로 반복 횟수 정의

Symbol.iterator에 generator 함수 연결

  • Object{}에 Symbol.iterator를 작성하고
    • generator 함수를 연결하면
    • 반복할 때마다 yield 수행
const obj = {};
obj[Symbol.iterator] = function*(){
  yield 1;
  yield 2;
  yield 3;
};
log([...obj]);

// [1, 2, 3]
  • 연결 구조

    • Symbol.iterator의 __proto__에 제너레이터 오브젝트가 있는 구조
  • 제너레이터 오브젝트에

    • 이터레이터 오브젝트를 연결하여 값을 공유하는 형태
    • 제너레이터 오브젝트에 이터레이터 오브젝트가 포함된 구조
const get = function*(){
  yield 10;
  yield 20;
};
const genObj = gen();
log(genObj.next());

const obj = genObj[Symbol.iterator]();
log(obj.next());

// {value: 10, done: false}
// {value: 20, done: false}

Symbol.match

Well-Known Symbol

  • Well-Known Symbol을 지원하는 String 메소드

    • match()
    • replace()
    • search()
    • split()
  • String.prototype.match()

    • 문자열에 패턴을 매치하고
    • 매치된 결과를 배열로 반환
const result = "Sports".match(/s/);
log(result);

// [s]

Symbol.match()

  • 개발자 코드를 함수 블록에 작성
  • String.prototype.match() 대신에 Symbol.match()가 실행됨
const sports = {
  base: "ball",
  [Symbol.match](value){
    return this.base.indexOf(value) < 0 ? "없음" : "있음";
  }
}
log("al".atch(sports));

// 있음
  • Symbol.match = false
    • //를 패턴으로 인식하지 않고 문자열로 인식
try {
  "/book/".startsWith(/book/);
} catch {
  log("정규 표현식으로 처리");
};

let check = /book/;
check[Symbol.match] = false;
log("/book/".startsWith(check));

// 정규 표현식으로 처리
// true
  • 메소드를 오버라이드하는 것이므로
    • 메소드의 시맨틱은 유지해야 함

0개의 댓글