prototype을 공부하던 중 instanceof 정확한 동작방식에 대해 제대로 알지 못했던 것 같아 정리합니다.
instanceof 연산자는 보통 프로토타입 체인을 통해 인스턴스의 여부, 상속의 여부를 판단합니다. 추가로 정적인 Symbol.hasInstance 메서드를 통해 커스텀할 수도 있습니다.
// obj instanceof Class는 다음과 같이 연산됩니다.
obj.__proto__ === Class.prototype
obj.__proto__.__proto__ === Class.prototype
obj.__proto__.__proto__.__proto__ === Class.prototype
...
// 하나라도 true를 반환하면 true
// 프로토타입 체인을 끝까지 따라갔음에도 Class.prototype과 같은 프로토타입이 없으면 false
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은 다음과 같이 동작합니다.
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
(**)
가 인스턴스를 만들기 전에 있는지 후에 있는지의 차이에 따라 다른 결과가 나타납니다. 어떻게 된 것일까요😕// 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__
은 바뀌지 않습니다.
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임을 확인하는 로직이 있는 것을 알 수 있습니다.