Study JavaScript 0506 - 1hr Symbol, ToPrimitive

변승훈·2022년 5월 9일
0

Study JavaScript

목록 보기
14/43

1. 심볼형

1-1. 심볼

유일한 식별자를 만들고 싶을 때 사용한다.
심볼을 만들 때, 심볼 이름이라 불리는 설명을 붙일 수 있고 디버깅 시 아주 유용하다.

심볼은 유일성이 보장되는 자료형이기 때문에 설명이 동일한 심볼을 여러 개 만들어도 각 심볼 값은 다르다.

심볼 이름은 어떤 것에도 영향을 주지 않는 이름표 역할만을 한다.

      let id = Symbol();

      // "id"라는 설명(심볼 이름)
      let id1 = Symbol("id");
      let id2 = Symbol("id");

      // 유일성을 보장하여 심볼 이름은 어떤 것에도 영향을 주지 않는다.
      console.log(id1 == id2);  // false

심볼은 문자형으로 자동 형 변환이 되지 않기 때문에 출력을 원한다면 .toString()메소드를 명시적으로 호출해줘야 된다.
설명만 보려면 symbol.description프로퍼티를 이용한다.

      let id = Symbol();
      console.log(id) // TypeError

      id = Symbol("id");
      console.log(id.toString()); // Symbol(id)
      console.log(id.description);  // id

1-2. '숨김' 프로퍼티

심볼을 이용하면 '숨김(hidden)'프로퍼티를 만들 수 있다. 숨김 프로퍼티는 외부 코드에서 접근이 불가능하고 값도 덮어쓸 수 없는 프로퍼티다.

예를 들어 다른 코드(써드파티같은 라이브러리 플러그인 등...)에서 가지고 온 특정 객체를 식별해야하는 상황이 왔다면 심볼을 사용해 식별자를 부여할 수 있으며 충돌을 방지할 수 있다.

      // 1.
      let user = {
        name: 'Hun'
      };

      let id = Symbol("id");

      user[id] = 1;
      console.log(user[id]);  // 심볼을 키로 사용해 데이터에 접근 가능

      // 2.
      let user = {
        name: 'Hun'
      };

      // 이 경우 값이 덮어 쓰이게 되서 무의미 해진다.
      user.id = "스크립트 id 값"
      user.id = "다른 코드 id 값"

Symbol in a literal

객체 리터럴을 사용해 객체를 만든 경우 대괄호를 사용해 심볼형 키를 만들어야 한다.

      let id = Symbol("id");

      let user = {
        name: "Hun",
        [id]: 123 // "id": 123은 안됨
      };

      // ### for in에서의 배제
      // 심볼은 for in 반복문에서 배제된다.
      let id = Symbol("id");
      let user = {
        name: "Hun",
        age: 30,
        [id]: 123
      };

      for (let key in user) alert(key); // name과 age만 출력되고, 심볼은 출력되지 않는다.

      // 심볼로 직접 접근하면 잘 작동한다.
      alert( "직접 접근한 값: " + user[id] );

1-3. 전역심볼

전역 심볼 레지스트리 안에 있는 심볼을 전역 심볼이라 한다. 이름이 같은 심볼이 같은 개체를 가리키길 원하는 경우에 사용한다.

전역 심볼 레지스트리 안에 심볼을 만들고 해당 심볼에 접근하면, 이름이 같은 경우 항상 동일한 심볼을 반환해준다.

레지스트리 안에 있는 심볼을 읽거나, 새로운 심볼을 생성하려면 Symbol.for(key)를 사용한다.

이 메소드를 호출하면 이름이 key인 심볼을 반환하고 조건에 맞는 심볼이 레지스트리 안에 없으면 새로운 심볼 Symbol(key)을 만들고 레지스트리 안에 저장한다.

      // 전역 레지스트리에서 심볼을 읽는다.
      let id = Symbol.for("id"); 

      // 동일한 이름을 이용해 심볼을 다시 읽는다.
      let idAgain = Symbol.for("id");

      // 두 심볼은 같다.
      alert( id === idAgain ); // true

Symbol.keyFor

전역 심볼을 찾을 때 사용되는 Symbol.for(key)에 반대되는 메소드도 있다. Symbol.keyFor(sym)를 사용하면 이름을 얻을 수 있다.

      // 이름을 이용해 심볼을 찾음
      let sym = Symbol.for("name");
      let sym2 = Symbol.for("id");

      // 심볼을 이용해 이름을 얻음
      alert( Symbol.keyFor(sym) ); // name
      alert( Symbol.keyFor(sym2) ); // id

전역 심볼이 아닌 인자가 넘어오면 Symbol.keyFor는 undefined를 반환한다.
전역 심볼이 아닌 모든 심볼은 description 프로퍼티가 있다. 일반 심볼에서 이름을 얻고 싶으면 description 프로퍼티를 사용하면 된다.

      let globalSymbol = Symbol.for("name");
      let localSymbol = Symbol("name");

      alert( Symbol.keyFor(globalSymbol) ); // name, 전역 심볼
      alert( Symbol.keyFor(localSymbol) ); // undefined, 전역 심볼이 아님

      alert( localSymbol.description ); // name

1-4. 시스템 심볼

javascript 내부에서 사용되는 심볼이다. 시스템 심볼을 활용하면 객체를 미세 조정할 수 있다.

  • Symbol.hasInstance
  • Symbol.isConcatSpreadable
  • Symbol.iterator
  • Symbol.toPrimitive
  • 등등...

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

  1. 객체는 논리 평가 시 true를 반환한다. 객체는 숫자형이나 문자형으로만 형 변환이 일어난다고 생각하면 된다.
  2. 숫자형으로의 형 변환은 객체끼리 빼는 연산을 할 때나 수학 관련 함수를 적용할 때 일어난다.
  3. 문자형으로의 형 변환은 대개 객체를 출력하려고 할 때 일어난다.

2-1. ToPrimitive

특수 객체 메소드를 의미하며 이를 사용하면 숫자형이나 문자형으로의 형 변환을 원하는 대로 조절할 수 있다.
객체 형 변환은 크게 세 종류로 구분되며 'hint'라 불리는 값이 구분 기준이 된다. 'hint'는 '목표로 하는 자료형'정도로 이해하면 된다.

  1. string
    문자열을 기대하는 연산을 수행할 때는(객체-문자형 변환), 'hint'가 string이 된다.

  2. number
    수학 연산을 적용하려 할 때(객체-숫자형 변환), 'hint'는 number가 된다.

  3. default
    연산자가 기대하는 자료형이 확실치 않을 때 'hint'는 default가 된다.
    '+' : 피연사자의 자료형에 따라, 인수가 객체일 때
    '==' : 객체-문자형, 객체-숫자형, 객체-심볼형끼리 비교 시 어떤 자료형으로 바꿔야 할지 확신이 서지 않을 때

2-2. Symbol.toPrimitive

목표로 하는 자료형(hint)을 명명하는데 사용된다.

      obj[Symbol.toPrimitive] = function(hint) {
        // 반드시 원시값을 반환해야 한다.
        // hint는 "string", "number", "default" 중 하나가 될 수 있다.
      };

실제 예시를 만들어 보자. hint에 따라 문자열, 숫자열로 변환될 수 있게 해 두었다.
user[Symbol.toPrimitive]를 사용하면 메서드 하나로 모든 종류의 형 변환을 다룰 수 있다.

      let user = {
      name: "Hun",
      money: 1000,

      [Symbol.toPrimitive](hint) {
        alert(`hint: ${hint}`);
        return hint == "string" ? `{name: "${this.name}"}` : this.money;
        }
      };

      // 데모:
      alert(user); // hint: string -> {name: "Hun"}
      alert(+user); // hint: number -> 1000
      alert(user + 500); // hint: default -> 1500

2-3. toString과 valueOf

객체에 Symbol.toPrimitive가 없으면 자바스크립트는 아래 규칙에 따라 toString이나 valueOf를 호출한다.

  • hint가 'string’인 경우: toString -> valueOf 순(toString이 있다면 toString을 호출, toString이 없다면 valueOf를 호출함)
  • 그 외: valueOf -> toString 순

이 메서드들은 반드시 원시값을 반환해야하며 toString이나 valueOf가 객체를 반환하면 그 결과는 무시된다.

일반 객체는 기본적으로 toString과 valueOf에 적용되는 다음 규칙을 따른다.

  • toString은 문자열 "[object Object]"을 반환한다.
  • valueOf는 객체 자신을 반환한다.
      let user = {name: "Hun"};

      alert(user); // [object Object]
      alert(user.valueOf() === user); // true

      // 위의 `Symbol.toPrimitive`의 예시를 기반으로 다시 구현해보면
      let user = {
        name: "Hun",
        money: 1000,

        // hint가 "string"인 경우
        toString() {
          return `{name: "${this.name}"}`;
        },

        // hint가 "number"나 "default"인 경우
        valueOf() {
          return this.money;
        }

      };

      alert(user); // toString -> {name: "John"}
      alert(+user); // valueOf -> 1000
      alert(user + 500); // valueOf -> 1500

출력 결과가 Symbol.toPrimitive를 사용한 예제와 완전히 동일하다는 걸 확인할 수 있다.

간혹 모든 형 변환을 한 곳에서 처리해야하는 경우도 생기는데 이럴 땐 toString만 구현해 주면 된다.
객체에 Symbol.toPrimitivevalueOf가 없으면, toString이 모든 형 변환을 처리한다.

2-4. 반환 타입

위의 3 개의 메소드는 'hint'에 명시된 자료형으로의 형 변환으로 보장해 주지 않는다. 객체가 아닌 원시값을 반환해 주는건 확실하다.

2-5. 추가 형 변환

'*'(곱셈)은 피연산자를 숫자형으로 변환 시킨다.
하지만 이항 덧셈 연산자는 피연산자에 문자열이 있다면 문자열을 연결한다.

profile
잘 할 수 있는 개발자가 되기 위하여

0개의 댓글