Javascript instanceof의 동작방식

blackbell·2020년 7월 6일
1

javascript

목록 보기
5/6

prototype을 공부하던 중 instanceof 정확한 동작방식에 대해 제대로 알지 못했던 것 같아 정리합니다.

instanceof 연산자는 보통 프로토타입 체인을 통해 인스턴스의 여부, 상속의 여부를 판단합니다. 추가로 정적인 Symbol.hasInstance 메서드를 통해 커스텀할 수도 있습니다.

1. 프로토타입 체인을 통한 인스턴스의 여부, 상속의 여부 판단

// obj instanceof Class는 다음과 같이 연산됩니다.
obj.__proto__ === Class.prototype
obj.__proto__.__proto__ === Class.prototype
obj.__proto__.__proto__.__proto__ === Class.prototype
...
// 하나라도 true를 반환하면 true
// 프로토타입 체인을 끝까지 따라갔음에도 Class.prototype과 같은 프로토타입이 없으면 false

2. static Symbol.hasInstance 메서드를 이용한 커스텀

class에 정적 메서드 Symbol.hasInstance가 구현되어 있으면 아례 예시와 같이 작동하고, 그렇지 않은 경우 위의 1번처럼 작동합니다.

class Temp {
  	static [Symbol.hasInstance](obj){
        // obj의 temp 프로퍼티가 있는지 확인
        return obj.hasOwnProperty('temp');
    }
}

const obj = {temp: 0};
console.log(obj instanceof Temp); // true
console.log({} instanceof Temp); // false

즉, v instanceof target은 다음과 같이 동작합니다.

  1. target의 타입이 Object가 아닐경우, TypeError 발생.
    (참고, js의 타입은 Undefined, Null, Boolean, String, Symbol, Number, BigInt, and Object )
  2. Symbol.hasInstance 메서드를 가졌는지 확인
    • 가졌다면 반환하는 값에 따라 결정.
  3. target이 callable이 아니라면, TypeError 발생.
  4. 그 외의 경우 1번과 같은 방식으로 결정.

여러가지 예

1. 위의 instanceof 연산자가 어떻게 동작하는지 안다면 constructor를 바꾸는 것은 의미가 없음을 알 수 있습니다.

function Temp(){}
Temp.prototype.constructor = Array;
 
const temp = new Temp;
console.log(temp.constructor === Array) //true
console.log(temp instanceof Temp) // true
console.log(temp instanceof Array) //false

2. 이것은 좀 엇나가는 얘기일 수 있습니다. (**)가 인스턴스를 만들기 전에 있는지 후에 있는지의 차이에 따라 다른 결과가 나타납니다. 어떻게 된 것일까요😕

// 1번 예시
{
  function Temp(){}
  Temp.prototype.constructor = Array;

  const temp = new Temp;
  console.log(temp instanceof Temp) // true (*)

  Temp.prototype = Array.prototype; // (**)
  console.log(temp instanceof Array) // false
}
// 2번 예시
{
  function Temp(){}
  Temp.prototype.constructor = Array;
  Temp.prototype = Array.prototype; // (**)
  
  const temp = new Temp;
  console.log(temp instanceof Temp) // true
  console.log(temp instanceof Array) // true
}

1번 예시의 (**)전후의 Temp.prototype을 살펴보면 Array.prototype로 바뀐 것을 알 수 있습니다. 그리고 instanceof 연산자는 temp instanceof Array를 확인할 때 temp.__proto__ === Array.prototype을 체크할 것입니다. 그렇다면 temp.__proto__ === Temp.prototype (*)일 것이고, (**)에서 Temp.prototype = Array.prototype이라고 했기 때문에 결국 temp.__proto__ === Array.prototype가 성립할 것이라고 생각했지만 결과는 false입니다.😡


2번 예시는 위의 생각대로 잘 결과가 나옵니다. 하지만 1번 예시의 결과가 다른 이유는 요즘 브라우저는 오브젝트를 생성하면 즉시 캐시객체를 내부에 생성하고 프로토타입 체인으로 얻어야하는 모든 키를 그 캐시객체에 미리 잡아두기 때문입니다. 체인을 통해 값을 얻는게 아니라 캐시객체에 질의하면 체인과정 없이 즉시 얻을 수 있기 때문에 프로토타입 체인보다 비용이 덜 들게 됩니다. 하지만 이것으로 인해 1번의 예시가 다음과 같은 결과가 나옵니다.
즉, Temp.prototype은 바뀌었지만 브라우저가 오브젝트를 생성할 때 캐시객체에 모든 키를 잡아뒀기 때문에 temp 인스턴스의 __proto__은 바뀌지 않습니다.

3. Temp.prototype을 바꾸면 당연히 instanceof의 값은 달라질 것입니다.

function Temp(){}
const temp = new Temp;
console.log(temp instanceof Temp); // true
console.log(Object.getPrototypeOf(temp) === Temp.prototype); // true
Temp.prototype = {};
console.log(Object.getPrototypeOf(temp)); // 위에서 설명했듯이 해당 값은 바뀌지 않는다.
console.log(temp instanceof Temp); // false

그렇다면 아래는 성립할까??

console.log(temp instanceof {prototype: temp.__proto__});
// VM58476:8 Uncaught TypeError: Right-hand side of 'instanceof' is not callable

스펙문서를 확인해보면 instanceof의 Right-hand side가 callable임을 확인하는 로직이 있는 것을 알 수 있습니다.

출처

profile
알고 싶은게 많은 프론트엔드 개발자입니다.

0개의 댓글