instanceof

instanceof

instanceof 연산자는 생성자의 prototype 속성이 객체의 프로토타입 체인 어딘가에 나타나는지 검사합니다. 반환값은 불리언 값입니다. 그 동작은 Symbol.hasInstance로 사용자 정의할 수 있습니다. Baseline Widely available — 2015년 7월부터 여러 브라우저에서 지원되는 잘 정착된 기능입니다.

출처: instanceof

본문

instanceof 연산자는 생성자의 prototype 속성이 객체의 프로토타입 체인 어딘가에 나타나는지를 검사합니다. 반환값은 불리언입니다. 그 동작은 Symbol.hasInstance로 사용자 정의할 수 있습니다.

시도해 보기 (Try it)

function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}
const auto = new Car("Honda", "Accord", 1998);

console.log(auto instanceof Car);
// Expected output: true

console.log(auto instanceof Object);
// Expected output: true

구문 (Syntax)

object instanceof constructor

매개변수 (Parameters)

  • object: 검사할 객체입니다.
  • constructor: 검사 대상이 되는 생성자입니다.

예외 (Exceptions)

  • TypeError: constructor가 객체가 아니면 발생합니다. constructor[Symbol.hasInstance]() 메서드가 없다면, 그것은 함수여야 합니다.

설명 (Description)

instanceof 연산자는 constructor.prototypeobject의 프로토타입 체인에 존재하는지 검사합니다. 이는 보통(항상은 아니지만) objectconstructor로 생성되었음을 의미합니다.

// defining constructors
function C() {}
function D() {}

const o = new C();

// true, because: Object.getPrototypeOf(o) === C.prototype
o instanceof C;

// false, because D.prototype is nowhere in o's prototype chain
o instanceof D;

o instanceof Object; // true, because:
C.prototype instanceof Object; // true

// Re-assign `constructor.prototype`: you should
// rarely do this in practice.
C.prototype = {};
const o2 = new C();

o2 instanceof C; // true

// false, because C.prototype is nowhere in
// o's prototype chain anymore
o instanceof C;

D.prototype = new C(); // add C to [[Prototype]] linkage of D
const o3 = new D();
o3 instanceof D; // true
o3 instanceof C; // true since C.prototype is now in o3's prototype chain

instanceof 테스트의 값은 객체를 만든 후 constructor.prototype이 재할당되면 바뀔 수 있습니다(보통 권장되지 않음). 또한 Object.setPrototypeOf를 사용해 객체의 프로토타입을 바꾸는 방식으로도 바뀔 수 있습니다.

클래스도 prototype 속성을 가지고 있으므로 같은 방식으로 동작합니다.

class A {}
class B extends A {}

const o1 = new A();
// true, because Object.getPrototypeOf(o1) === A.prototype
o1 instanceof A;
// false, because B.prototype is nowhere in o1's prototype chain
o1 instanceof B;

const o2 = new B();
// true, because Object.getPrototypeOf(Object.getPrototypeOf(o2)) === A.prototype
o2 instanceof A;
// true, because Object.getPrototypeOf(o2) === B.prototype
o2 instanceof B;

바인딩된 함수(bound function)의 경우, 바인딩된 함수는 prototype이 없으므로 instanceof는 대상 함수에서 prototype 속성을 찾습니다.

class Base {}
const BoundBase = Base.bind(null, 1, 2);
console.log(new Base() instanceof BoundBase); // true

instanceof와 Symbol.hasInstance

constructorSymbol.hasInstance 메서드가 있으면, 그 메서드가 우선적으로 호출되며 object를 유일한 인자로, constructorthis로 받습니다.

// This class allows plain objects to be disguised as this class's instance,
// as long as the object has a particular flag as its property.
class Forgeable {
  static isInstanceFlag = Symbol("isInstanceFlag");

  static [Symbol.hasInstance](obj) {
    return Forgeable.isInstanceFlag in obj;
  }
}

const obj = { [Forgeable.isInstanceFlag]: true };
console.log(obj instanceof Forgeable); // true

모든 함수는 기본적으로 Function.prototype에서 상속되므로, 대부분의 경우 Function.prototype[Symbol.hasInstance]() 메서드가 오른쪽이 함수일 때의 instanceof 동작을 지정합니다. instanceof의 정확한 알고리즘은 Symbol.hasInstance 페이지를 참고하세요.

instanceof와 여러 realm

JavaScript 실행 환경(창, 프레임 등)은 각각 고유한 realm에 있습니다. 이는 서로 다른 내장 기능(서로 다른 전역 객체, 서로 다른 생성자 등)을 가진다는 뜻입니다. 이는 예상치 못한 결과를 초래할 수 있습니다. 예를 들어 [] instanceof window.frames[0].Arrayfalse를 반환하는데, Array.prototype !== window.frames[0].Array.prototype이고 현재 realm의 배열이 전자에서 상속되기 때문입니다.

이것은 처음에는 이해가 안 될 수 있지만, 여러 프레임이나 창을 다루고 함수를 통해 객체를 한 컨텍스트에서 다른 컨텍스트로 전달하는 스크립트에서는 유효하고 강력한 이슈입니다. 예를 들어 Array.isArray()를 사용하면 어떤 realm에서 왔는지와 무관하게 주어진 객체가 실제로 Array인지 안전하게 확인할 수 있습니다.

예를 들어 어떤 컨텍스트에서 NodeSVGElement인지 확인하려면 myNode instanceof myNode.ownerDocument.defaultView.SVGElement를 사용할 수 있습니다.

예제 (Examples)

String과 함께 instanceof 사용하기

다음 예제는 String 객체와 함께 사용할 때의 instanceof 동작을 보여줍니다.

const literalString = "This is a literal string";
const stringObject = new String("String created with constructor");

literalString instanceof String; // false, string primitive is not a String
stringObject instanceof String; // true

literalString instanceof Object; // false, string primitive is not an Object
stringObject instanceof Object; // true

stringObject instanceof Date; // false

Map과 함께 instanceof 사용하기

다음 예제는 Map 객체와 함께 사용할 때의 instanceof 동작을 보여줍니다.

const myMap = new Map();

myMap instanceof Map; // true
myMap instanceof Object; // true
myMap instanceof String; // false

Object.create()로 만든 객체

다음 예제는 Object.create()로 만든 객체와 함께 사용할 때의 instanceof 동작을 보여줍니다.

function Shape() {}

function Rectangle() {
  Shape.call(this); // call super constructor.
}

Rectangle.prototype = Object.create(Shape.prototype);

Rectangle.prototype.constructor = Rectangle;

const rect = new Rectangle();

rect instanceof Object; // true
rect instanceof Shape; // true
rect instanceof Rectangle; // true
rect instanceof String; // false

const literalObject = {};
const nullObject = Object.create(null);
nullObject.name = "My object";

literalObject instanceof Object; // true, every object literal has Object.prototype as prototype
({}) instanceof Object; // true, same case as above
nullObject instanceof Object; // false, prototype is end of prototype chain (null)

myCar가 Car 타입이자 Object 타입임을 보여주기

다음 코드는 객체 타입 Car와 그 객체 타입의 인스턴스 myCar를 만듭니다. instanceof 연산자는 myCar 객체가 Car 타입이면서 Object 타입임을 보여줍니다.

function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}
const myCar = new Car("Honda", "Accord", 1998);
const a = myCar instanceof Car; // returns true
const b = myCar instanceof Object; // returns true

instanceof가 아님 (Not an instanceof)

객체가 특정 생성자의 인스턴스가 아닌지 검사하려면 다음처럼 할 수 있습니다:

if (!(myCar instanceof Car)) {
  // Do something, like:
  // myCar = new Car(myCar)
}

이것은 다음 코드와 정말로 다릅니다:

if (!myCar instanceof Car) {
  // unreachable code
}

이것은 항상 false입니다. (!myCarinstanceof보다 먼저 평가되므로, 항상 어떤 불리언이 Car의 인스턴스인지 확인하려는 셈입니다.)

instanceof의 동작 덮어쓰기

instanceof를 사용할 때 흔한 함정은, x instanceof C라면 xC를 생성자로 사용해 만들어졌다고 믿는 것입니다. 이것은 사실이 아닌데, xC.prototype을 프로토타입으로 직접 할당받았을 수 있기 때문입니다. 이런 경우 코드가 C의 비공개 필드를 x에서 읽으려 해도 여전히 실패합니다:

class C {
  #value = "foo";
  static getValue(x) {
    return x.#value;
  }
}

const x = { __proto__: C.prototype };

if (x instanceof C) {
  console.log(C.getValue(x)); // TypeError: Cannot read private member #value from an object whose class did not declare it
}

이를 피하려면 CSymbol.hasInstance 메서드를 추가해 instanceof의 동작을 덮어쓸 수 있으며, in으로 브랜드 검사(branded check)를 수행합니다:

class C {
  #value = "foo";

  static [Symbol.hasInstance](x) {
    return #value in x;
  }

  static getValue(x) {
    return x.#value;
  }
}

const x = { __proto__: C.prototype };

if (x instanceof C) {
  // Doesn't run, because x is not a C
  console.log(C.getValue(x));
}

이 동작을 현재 클래스로 제한하고 싶을 수 있다는 점에 유의하세요; 그렇지 않으면 하위 클래스에 대해 오탐(false positive)을 일으킬 수 있습니다:

class D extends C {}
console.log(new C() instanceof D); // true; because D inherits [Symbol.hasInstance] from C

this가 현재 생성자인지 확인하는 방식으로 이 작업을 수행할 수 있습니다:

class C {
  #value = "foo";

  static [Symbol.hasInstance](x) {
    return this === C && #value in x;
  }
}

class D extends C {}
console.log(new C() instanceof D); // false
console.log(new C() instanceof C); // true
console.log({ __proto__: C.prototype } instanceof C); // false

명세 (Specifications)

instanceof 연산자는 ECMAScript 언어 명세에 정의되어 있습니다.

브라우저 호환성 (Browser compatibility)

이 기능은 대부분의 현대 브라우저에서 널리 지원됩니다(Baseline Widely available). 자세한 호환성 표는 MDN의 브라우저 호환성 섹션을 참고하세요.

더 알아보기