9. 클래스(2)

9.4 private, protected 프로퍼티와 메서드


요약

  • 객체 지향 프로그래밍에선 내부 인터페이스와 외부 인터페이스를 구분하는 것을 [캡슐화(encapsulation)]라는 용어를 사용해 설명합니다.
  • 캡슐화는 이점은 다음과 같습니다.

1. 사용자가 자신의 발등을 찍지 않도록 보호
커피 머신를 함께 사용하는 개발팀이 있다고 상상해봅시다. "Best CoffeeMachine"이라는 회사에서 만든 이 커피 머신은 현재 잘 작동하고 있지만, 보호 커버가 없어서 내부 인터페이스가 노출되어있는 상황입니다.
교양있는 팀원들은 모두 설계 의도에 맞게 커피 머신을 사용합니다. 그런데 어느 날 John이라는 개발자가 자신의 능력을 과신하며 커피 머신 내부를 살짝 만지게 됩니다. 이틀 후, 커피 머신은 고장이 나버렸죠.
커피 머신이 고장 난 건 John의 잘못이라기보다는, 보호 커버를 없애고 John이 마음대로 조작하도록 내버려 둔 사람의 잘못입니다.
프로그래밍에서도 마찬가지입니다. 외부에서 의도치 않게 클래스를 조작하게 되면 그 결과는 예측할 수 없게 됩니다.

2. 지원 가능
실제 개발 과정에서 일어나는 상황은 커피 머신 사례보다 훨씬 복잡합니다. 커피 머신은 한번 구매하면 끝이지만 실제 코드는 유지보수가 끊임없이 일어나기 때문입니다.
내부 인터페이스를 엄격하게 구분하면, 클래스 개발자들은 사용자에게 알리지 않고도 자유롭게 내부 프로퍼티와 메서드들을 수정할 수 있습니다.
내부 인터페이스가 엄격히 구분된 클래스를 만지고 있다면, 그 어떤 외부 코드도 내부 private 메서드에 의존하고 있지 않기 때문에 private 메서드의 이름을 안전하게 바꿀 수 있고, 매개변수를 변경하거나 없앨 수도 있다는 것을 알아 두면 됩니다.
사용자 입장에선 새로운 버전이 출시되면서 내부 정비가 전면적으로 이뤄졌더라도 외부 인터페이스만 똑같다면 업그레이드가 용이하다는 장점이 있습니다.

3. 복잡성 은닉
사람들은 간단한 것을 좋아합니다. 내부는 간단치 않더라도 최소한 외형은 간단해야 하죠.
프로그래머들도 예외는 아닙니다.
구현 세부 사항이 숨겨져 있으면 간단하고 편리해집니다. 외부 인터페이스에 대한 설명도 문서화하기 쉬워지죠.

내부 인터페이스를 숨기려면 protectedprivate 프로퍼티를 사용하면 됩니다.

  • protected 필드는 _로 시작합니다. _은 자바스크립트에서 지원하는 문법은 아니지만, protected 필드를 나타낼 때 관습처럼 사용됩니다. 개발자는 protected 프로퍼티가 정의된 클래스와 해당 클래스를 상속받는 클래스에서만 _가 붙은 필드에 접근해야 합니다.
  • private 필드는 #로 시작하며, 자바스크립트에서 지원하는 문법입니다. #로 시작하는 필드는 해당 필드가 정의된 클래스 내부에서만 접근 가능합니다.
    모든 브라우저에서 private 필드를 지원하진 않지만 폴리필을 구현하여 사용할 수 있습니다.
  • 객체 지향 프로그래밍에서 가장 중요한 원리 중 하나는 '내부 인터페이스와 외부 인터페이스를 구분 짓는 것’입니다.
  • 단순히 'hello word’를 출력하는 것이 아닌 복잡한 애플리케이션을 구현하려면, 내부 인터페이스와 외부 인터페이스를 구분하는 방법을 ‘반드시’ 알고 계셔야 합니다.

실생활 예제

  • 커피 머신은 꽤 믿음직한 기계입니다. 수년 간 사용할 수 있고, 중간에 고장이 나도 수리를 받으면 됩니다.
  • 외형은 단순하지만 커피 머신을 신뢰할 수 있는 이유는 모든 세부 요소들이 기계 내부에 잘 정리되어 숨겨져 있기 때문입니다.
  • 커피 머신에서 보호 커버를 제거하면 사용법이 훨씬 복잡해지고 위험한 상황이 생길 수 있습니다. 어디를 눌러야 할지 모르고 감전이 될 수도 있기 때문입니다.
  • 앞으로 학습하겠지만, 프로그래밍에서 객체는 커피 머신과 같습니다. 프로그래밍에서는 보호 커버를 사용하는 대신 특별한 문법과 컨벤션을 사용해 안쪽 세부 사항을 숨긴다는 점이 다릅니다.

내부 인터페이스와 외부 인터페이스

  • 객체 지향 프로그래밍에서 프로퍼티와 메서드는 두 그룹으로 분류됩니다.
    - 내부 인터페이스(internal interface) – 동일한 클래스 내의 다른 메서드에선 접근할 수 있지만, 클래스 밖에선 접근할 수 없는 프로퍼티와 메서드
    - 외부 인터페이스(external interface) – 클래스 밖에서도 접근 가능한 프로퍼티와 메서드
  • 커피 머신으로 비유하자면 기계 안쪽에 숨어있는 뜨거운 물이 지나가는 관이나 발열 장치 등이 내부 인터페이스가 될 수 있습니다.

  • 내부 인터페이스의 세부사항들은 서로의 정보를 이용하여 객체를 동작시킵니다. 발열 장치에 부착된 관을 통해 뜨거운 물이 이동하는 것처럼 말이죠.

  • 그런데 커피 머신은 보호 커버에 둘러싸여 있기 때문에 보호 커버를 벗기지 않고는 커피머신 외부에서 내부로 접근할 수 없습니다. 밖에선 세부 요소를 알 수 없고, 접근도 불가능합니다. 내부 인터페이스의 기능은 외부 인터페이스를 통해야만 사용할 수 있습니다.

  • 이런 특징 때문에 외부 인터페이스만 알아도 객체를 가지고 무언가를 할 수 있습니다. 객체 안이 어떻게 동작하는지 알지 못해도 괜찮다는 점은 큰 장점으로 작용합니다.

  • 자바스크립트에는 아래와 같은 두 가지 타입의 객체 필드(프로퍼티와 메서드)가 있습니다.
    - public: 어디서든지 접근할 수 있으며 외부 인터페이스를 구성합니다. 지금까지 다룬 프로퍼티와 메서드는 모두 public입니다.
    - private: 클래스 내부에서만 접근할 수 있으며 내부 인터페이스를 구성할 때 쓰입니다.

  • 자바스크립트 이외의 다수 언어에서 클래스 자신과 자손 클래스에서만 접근을 허용하는 ‘protected’ 필드를 지원합니다. protected 필드는 private과 비슷하지만, 자손 클래스에서도 접근이 가능하다는 점이 다릅니다. protected 필드도 내부 인터페이스를 만들 때 유용합니다. 자손 클래스의 필드에 접근해야 하는 경우가 많기 때문에, protected 필드는 private 필드보다 조금 더 광범위하게 사용됩니다.
  • 자바스크립트는 protected 필드를 지원하지 않지만, protected를 사용하면 편리한 점이 많기 때문에 이를 모방해서 사용하는 경우가 많습니다.

프로퍼티 보호하기

class CoffeeMachine {
  waterAmount = 0; // 물통에 차 있는 물의 양

  constructor(power) {
    this.power = power;
    alert( `전력량이 ${power}인 커피머신을 만듭니다.` );
  }

}

// 커피 머신 생성
let coffeeMachine = new CoffeeMachine(100);
// 물 추가
coffeeMachine.waterAmount = 200;
  • 이제 waterAmountprotected로 바꿔서 waterAmount를 통제해 보겠습니다. 예시로 waterAmount0 미만의 값으로는 설정하지 못하도록 만들어 볼 겁니다.
  • protected 프로퍼티 명 앞엔 밑줄 _이 붙습니다.
    자바스크립트에서 강제한 사항은 아니지만, 밑줄은 프로그래머들 사이에서 외부 접근이 불가능한 프로퍼티나 메서드를 나타낼 때 씁니다.
class CoffeeMachine {
  _waterAmount = 0;

  set waterAmount(value) {
    if (value < 0) throw new Error("물의 양은 음수가 될 수 없습니다.");
    this._waterAmount = value;
  }
  get waterAmount() {
    return this._waterAmount;
  }
  constructor(power) {
    this._power = power;
  }

}

// 커피 머신 생성
let coffeeMachine = new CoffeeMachine(100);

// 물 추가
coffeeMachine.waterAmount = -10; // Error: 물의 양은 음수가 될 수 없습니다.

읽기 전용 프로퍼티

  • power 프로퍼티를 읽기만 가능하도록 만들어봅시다. 프로퍼티를 생성할 때만 값을 할당할 수 있고, 그 이후에는 값을 절대 수정하지 말아야 하는 경우가 종종 있는데, 이럴 때 읽기 전용 프로퍼티를 활용할 수 있습니다.
  • 읽기 전용 프로퍼티를 만들려면 setter(설정자)는 만들지 않고 getter(획득자)만 만들어야 합니다.
    (constructor에서 초기값 할당)
class CoffeeMachine {
  // ...
  constructor(power) {
    this._power = power;
  }
  get power() {
    return this._power;
  }
}

// 커피 머신 생성
let coffeeMachine = new CoffeeMachine(100);

alert(`전력량이 ${coffeeMachine.power}인 커피머신을 만듭니다.`); // 전력량이 100인 커피머신을 만듭니다.

coffeeMachine.power = 25; // Error (setter 없음)

getter와 setter 함수

  • 위에서는 get, set 문법을 사용해서 gettersetter 함수를 만들었습니다.
  • 하지만 대부분은 아래와 같이 get.../set... 형식의 함수가 선호됩니다.
class CoffeeMachine {
  _waterAmount = 0;
  setWaterAmount(value) {
    if (value < 0) throw new Error("물의 양은 음수가 될 수 없습니다.");
    this._waterAmount = value;
  }
  getWaterAmount() {
    return this._waterAmount;
  }
}
new CoffeeMachine().setWaterAmount(100);
  • 다소 길어보이긴 하지만, 이렇게 함수를 선언하면 다수의 인자를 받을 수 있기 때문에 좀 더 유연합니다(위 예시에선 인자가 하나뿐이긴 하지만요).
  • 반면 get, set 문법을 사용하면 코드가 짧아진다는 장점이 있습니다. 어떤걸 사용해야 한다는 규칙은 없으므로 원하는 방식을 선택해서 사용하세요.

protected 필드는 상속됩니다.

  • class MegaMachine extends CoffeeMachine로 클래스를 상속받으면, 새로운 클래스의 메서드에서 this._waterAmountthis._power를 사용해 프로퍼티에 접근할 수 있습니다.
  • 이렇게 protected 필드는 아래에서 보게 될 private 필드와 달리, 자연스러운 상속이 가능합니다.

private 프로퍼티

  • private 프로퍼티와 메서드는 제안(proposal) 목록에 등재된 문법으로, 명세서에 등재되기 직전 상태입니다.
  • private 프로퍼티와 메서드는 #으로 시작합니다. #이 붙으면 클래스 안에서만 접근할 수 있습니다.
  • 물 용량 한도를 나타내는 private 프로퍼티 #waterLimit과 남아있는 물의 양을 확인해주는 private 메서드 #checkWater를 구현해봅시다.
class CoffeeMachine {
  #waterLimit = 200;

  #checkWater(value) {
    if (value < 0) throw new Error("물의 양은 음수가 될 수 없습니다.");
    if (value > this.#waterLimit) throw new Error("물이 용량을 초과합니다.");
  }

}

let coffeeMachine = new CoffeeMachine();

// 클래스 외부에서 private에 접근할 수 없음
coffeeMachine.#checkWater(); // Error
coffeeMachine.#waterLimit = 1000; // Error
  • #은 자바스크립트에서 지원하는 문법으로, private 필드를 의미합니다. private 필드는 클래스 외부나 자손 클래스에서 접근할 수 없습니다.
  • private 필드는 public 필드와 상충하지 않습니다. private 프로퍼티 #waterAmountpublic 프로퍼티 waterAmount를 동시에 가질 수 있습니다.
  • #waterAmount의 접근자 waterAmount를 만들어봅시다.
class CoffeeMachine {

  #waterAmount = 0;

  get waterAmount() {
    return this.#waterAmount;
  }

  set waterAmount(value) {
    if (value < 0) throw new Error("물의 양은 음수가 될 수 없습니다.");
    this.#waterAmount = value;
  }
}

let machine = new CoffeeMachine();

machine.waterAmount = 100;
alert(machine.#waterAmount); // Error
  • protected 필드와 달리, private 필드는 언어 자체에 의해 강제된다는 점이 장점입니다.
  • 그런데 CoffeeMachine을 상속받는 클래스에선 #waterAmount에 직접 접근할 수 없습니다. #waterAmount에 접근하려면 waterAmountgettersetter를 통해야 합니다.
class MegaCoffeeMachine extends CoffeeMachine {
  method() {
    alert( this.#waterAmount ); // Error: CoffeeMachine을 통해서만 접근할 수 있습니다.
  }
}
  • 다양한 시나리오에서 이런 제약사항은 너무 엄격합니다. CoffeeMachine을 상속받는 클래스에선 CoffeeMachine의 내부에 접근해야 하는 정당한 사유가 있을 수 있기 때문이죠. 언어 차원에서 protected 필드를 지원하지 않아도 더 자주 쓰이는 이유가 바로 여기에 있습니다.

private 필드는 this[name]로 사용할 수 없습니다.

  • private 필드는 특별합니다. 알다시피, 보통은 this[name]을 사용해 필드에 접근할 수 있습니다.
  • 하지만 private 필드는 this[name]으로 접근할 수 없습니다. 이런 문법적 제약은 필드의 보안을 강화하기 위해 만들어졌습니다.
class User {
  ...
  sayHi() {
    let fieldName = "name";
    alert(`Hello, ${this[fieldName]}`);
  }
}


9.5 내장 클래스 확장하기


  • 배열, 맵 같은 내장 클래스도 확장 가능합니다.
// 메서드 하나를 추가합니다(더 많이 추가하는 것도 가능).
class PowerArray extends Array {
  isEmpty() {
    return this.length === 0;
  }
}

let arr = new PowerArray(1, 2, 5, 10, 50);
alert(arr.isEmpty()); // false

let filteredArr = arr.filter(item => item >= 10);
alert(filteredArr); // 10, 50
alert(filteredArr.isEmpty()); // false
  • 뭔가 흥미로운 점이 하나 보이네요. filter, map 등의 내장 메서드가 상속받은 클래스인 PowerArray의 인스턴스(객체)를 반환합니다. 이 객체를 구현할 땐 내부에서 객체의 constructor 프로퍼티를 사용합니다.
  • 따라서 아래와 같은 관계를 갖습니다.
    arr.constructor === PowerArray
  • arr.filter()가 호출될 때, 내부에선 기본 Array가 아닌 arr.constructor를 기반으로 새로운 배열이 만들어지고 여기에 필터 후 결과가 담깁니다. 이렇게 되면 PowerArray에 구현된 메서드를 사용할 수 있다는 장점이 생깁니다.
  • 물론 동작 방식을 변경할 수 있습니다.
  • 특수 정적 getterSymbol.species를 클래스에 추가할 수 있는데, Symbol.species가 있으면 map, filter 등의 메서드를 호출할 때 만들어지는 개체의 생성자를 지정할 수 있습니다. 원하는 생성자를 반환하기만 하면 되죠.
  • map이나 filter같은 내장 메서드가 일반 배열을 반환하도록 하려면 아래 예시처럼 Symbol.speciesArray를 반환하도록 해주면 됩니다.
class PowerArray extends Array {
  isEmpty() {
    return this.length === 0;
  }

  // 내장 메서드는 반환 값에 명시된 클래스를 생성자로 사용합니다.
  static get [Symbol.species]() {
    return Array;
  }
}

let arr = new PowerArray(1, 2, 5, 10, 50);
alert(arr.isEmpty()); // false

// filter는 arr.constructor[Symbol.species]를 생성자로 사용해 새로운 배열을 만듭니다.
let filteredArr = arr.filter(item => item >= 10);

// filteredArr는 PowerArray가 아닌 Array의 인스턴스입니다.
alert(filteredArr.isEmpty()); // Error: filteredArr.isEmpty is not a function
  • 보시다시피 이제 .filterArray를 반환합니다. 따라서 더는 확장 기능이 전달되지 않습니다.
  • Map, Set 같은 컬렉션도 위와 같이 동작합니다.

내장 객체와 정적 메서드 상속

  • 내장 객체는 Object.keys, Array.isArray 등의 자체 정적 메서드를 갖습니다.
  • 앞서 학습한 바와 같이 네이티브 클래스들은 서로 상속 관계를 맺습니다. ArrayObject를 상속받죠.
  • 일반적으론 한 클래스가 다른 클래스를 상속받으면 정적 메서드와 그렇지 않은 메서드 모두를 상속받습니다.
  • 그런데 내장 클래스는 다릅니다. 내장클래스는 정적 메서드를 상속받지 못합니다.
  • 예를 들어봅시다. ArrayDate는 모두 Object를 상속받기 때문에 두 클래스의 인스턴스에선 Object.prototype에 구현된 메서드를 사용할 수 있습니다. 그런데 Array.[[Prototype]]Date.[[Prototype]]Object를 참조하지 않기 때문에 Array.keys()Date.keys() 같은 정적 메서드를 인스턴스에서 사용할 수 없습니다.

  • 보시다시피 DateObject를 직접 이어주는 링크가 없습니다. DateObject는 독립적이죠. Date.prototypeObject.prototype를 상속받습니다.
  • 내장 객체 간의 상속과 extends를 사용한 상속의 가장 큰 차이점이 여기에 있습니다.

비교 : 내장 객체간의 상속이 아닌 정적 프로퍼티와 메서드 상속

(9.3 정적 메서드와 정적 프로퍼티)



9.6 'instanceof'로 클래스 확인하기


요약

동작대상반환값
typeof원시형문자열
{}.toString원시형, 내장 객체, Symbol.toStringTag가 있는 객체문자열
instanceof객체true나 false
  • 예시에서 보았듯이 {}.toString은 typeof보다 ‘기능이 더’ 많습니다.
  • instanceof 연산자는 계층 구조를 가진 클래스를 다룰 때나 클래스의 상속 여부를 확인하고자 할 때 그 진가를 발휘합니다.
  • instanceof 연산자를 사용하면 객체가 특정 클래스에 속하는지 아닌지를 확인할 수 있습니다. instanceof는 상속 관계도 확인해줍니다.
  • 확인 기능은 다양한 곳에서 쓰이는데, 이번 챕터에선 instanceof를 사용해 인수의 타입에 따라 이를 다르게 처리하는 다형적인(polymorphic) 함수를 만드는데 사용해보겠습니다.

instanceof 연산자

  • obj instanceof Class
  • objClass에 속하거나 Class를 상속받는 클래스에 속하면 true가 반환됩니다.
class Rabbit {}
let rabbit = new Rabbit();

// rabbit이 클래스 Rabbit의 객체인가요?
alert( rabbit instanceof Rabbit ); // true
  • instanceof는 생성자 함수에서도 사용할 수 있습니다.
// 클래스가 아닌 생성자 함수
function Rabbit() {}

alert( new Rabbit() instanceof Rabbit ); // true
  • Array 같은 내장 클래스에도 사용할 수 있습니다.
let arr = [1, 2, 3];
alert( arr instanceof Array ); // true
alert( arr instanceof Object ); // true
  • 위 예시에서 arr은 클래스 Object에도 속한다는 점에 주목해주시기 바랍니다. Array는 프로토타입 기반으로 Object를 상속받습니다.
  • instanceof 연산자는 보통, 프로토타입 체인을 거슬러 올라가며 인스턴스 여부나 상속 여부를 확인합니다. 그런데 정적 메서드 Symbol.hasInstance을 사용하면 직접 확인 로직을 설정할 수도 있습니다.
  • obj instanceof Class은 대략 아래와 같은 알고리즘으로 동작합니다.
  1. 클래스에 정적 메서드 Symbol.hasInstance가 구현되어 있으면, obj instanceof Class문이 실행될 때, Class[Symbol.hasInstance](obj)가 호출됩니다. 호출 결과는 truefalse이어야 합니다. 이런 규칙을 기반으로 instanceof의 동작을 커스터마이징 할 수 있습니다.
// canEat 프로퍼티가 있으면 animal이라고 판단할 수 있도록
// instanceOf의 로직을 직접 설정합니다.
class Animal {
  static [Symbol.hasInstance](obj) {
    if (obj.canEat) return true;
  }
}

let obj = { canEat: true };

alert(obj instanceof Animal); // true, Animal[Symbol.hasInstance](obj)가 호출됨
  1. 그런데, 대부분의 클래스엔 Symbol.hasInstance가 구현되어있지 않습니다. 이럴 땐 일반적인 로직이 사용됩니다. obj instanceOf ClassClass.prototypeobj 프로토타입 체인 상의 프로토타입 중 하나와 일치하는지 확인합니다. 비교는 차례 차례 진행됩니다.
obj.__proto__ === Class.prototype?
obj.__proto__.__proto__ === Class.prototype?
obj.__proto__.__proto__.__proto__ === Class.prototype?
...
// 이 중 하나라도 true라면 true를 반환합니다.
// 그렇지 않고 체인의 끝에 도달하면 false를 반환합니다.
  • 위 예시에서 rabbit.__proto__ === Rabbit.prototypetrue이기 때문에 instanceoftrue를 반환합니다.
  • 상속받은 클래스를 사용하는 경우엔 두 번째 단계에서 일치 여부가 확인됩니다.
class Animal {}
class Rabbit extends Animal {}

let rabbit = new Rabbit();
alert(rabbit instanceof Animal); // true

// rabbit.__proto__ === Rabbit.prototype
// rabbit.__proto__.__proto__ === Animal.prototype (일치!)

  • 한편, objAobjB의 프로토타입 체인 상 어딘가에 있으면 true를 반환해주는 메서드, objA.isPrototypeOf(objB)도 있습니다. obj instanceof ClassClass.prototype.isPrototypeOf(obj)와 동일합니다.
  • isPrototypeOfClass 생성자를 제외하고 포함 여부를 검사하는 점이 조금 특이합니다. 검사 시, 프로토타입 체인과 Class.prototype만 고려합니다.
  • isPrototypeOf의 이런 특징은 객체 생성 후 prototype 프로퍼티가 변경되는 경우 특이한 결과를 초래하기도 합니다. 아래와 같이 말이죠.
function Rabbit() {}
let rabbit = new Rabbit();

// 프로토타입이 변경됨
Rabbit.prototype = {};

// 더 이상 Rabbit이 아닙니다!
alert( rabbit instanceof Rabbit ); // false

보너스: 타입 확인을 위한 Object.prototype.toString

  • 일반 객체를 문자열로 변화하면 [object Object]가 된다는 것을 알고 계실 겁니다.
let obj = {};

alert(obj); // [object Object]
alert(obj.toString()); // 같은 결과가 출력됨
  • 이렇게 [object Object]가 되는 이유는 toString의 구현방식 때문입니다. 그런데 toStringtoString을 더 강력하게 만들어주는 기능이 숨겨져 있습니다. toString의 숨겨진 기능을 사용하면 확장 typeof, instanceof의 대안을 만들 수 있습니다.
  • 명세서에 따르면, 객체에서 내장 toString을 추출하는 게 가능합니다. 이렇게 추출한 메서드는 모든 값을 대상으로 실행할 수 있습니다. 호출 결과는 값에 따라 달라집니다.
    - 숫자형 – [object Number]
    - 불린형 – [object Boolean]
    - null[object Null]
    - undefined[object Undefined]
    - 배열 – [object Array]
    - 그외 – 커스터마이징 가능
// 편의를 위해 toString 메서드를 변수에 복사함
let objectToString = Object.prototype.toString;

// 아래 변수의 타입은 무엇일까요?
let arr = [];

alert( objectToString.call(arr) ); // [object Array]
  • call/apply와 데코레이터, 포워딩 챕터에서 설명한 call을 사용해 컨텍스트를 this=arr로 설정하고 함수 objectToString를 실행하였습니다.
  • toString 알고리즘은 내부적으로 this를 검사하고 상응하는 결과를 반환합니다. 예시를 더 살펴봅시다.
let s = Object.prototype.toString;

alert( s.call(123) ); // [object Number]
alert( s.call(null) ); // [object Null]
alert( s.call(alert) ); // [object Function]

Symbol.toStringTag

  • 특수 객체 프로퍼티 Symbol.toStringTag를 사용하면 toString의 동작을 커스터마이징 할 수 있습니다.
let user = {
  [Symbol.toStringTag]: "User"
};
alert( {}.toString.call(user) ); // [object User]
  • 대부분의 호스트 환경은 자체 객체에 이와 유사한 프로퍼티를 구현해 놓고 있습니다. 브라우저 관련 예시 몇 가지를 살펴봅시다.
// 특정 호스트 환경의 객체와 클래스에 구현된 toStringTag
alert( window[Symbol.toStringTag]); // Window
alert( XMLHttpRequest.prototype[Symbol.toStringTag] ); // XMLHttpRequest

alert( {}.toString.call(window) ); // [object Window]
alert( {}.toString.call(new XMLHttpRequest()) ); // [object XMLHttpRequest]
  • 실행 결과에서 보듯이 호스트 환경 고유 객체의 Symbol.toStringTag 값은 [object ...]로 쌓여진 값과 동일합니다.
  • 이처럼 ‘typeof’ 연산자의 강력한 변형들(toStringtoStringTag – 옮긴이)은 원시 자료형뿐만 아니라 내장 객체에도 사용할 수 있습니다. 그리고 커스터마이징까지 가능합니다.
  • 내장 객체의 타입 확인을 넘어서 타입을 문자열 형태로 받고 싶다면 instanceof 대신, {}.toString.call을 사용할 수 있습니다.


9.7





📚 참고 : javascript.info

좋은 웹페이지 즐겨찾기