Logo

자바스크립트의 프로토타입 체인과 instanceof 연산자

ES6에서 클래스 관련 문법이 지원되면서 자바스크립트도 어엿한 객체 지향 프로그래밍 언어로서 점점 자리를 잡아가고 있는 것 같습니다. 그에 따라서 자바스크립트로 코딩을 할 때 어떤 객체가 어떤 클래스의 인스턴스인지를 알아내야 할 때가 자주 생기는데요.

이번 포스팅에서는 이럴 때 instanceof 연산자를 사용하는 방법과 프로토타입 체인과 관련된 작동 원리에 대해서 알아보겠습니다.

instanceof 연산자

instanceof 연산자는 객체가 어떤 클래스의 인스턴스인지를 알아내기 위해서 사용합니다. 객체 instanceof 클래스의 형태로 사용하는데, 객체가 클래스의 인스턴스라면 결과로 참(true)이 나오고 아니라면 결과로 거짓(false)이 나옵니다.

여기서 꼭 알아두어야 할 점은 객체 지향 패러다임에서는 객체 그 객체를 직접 만들어낸 생성자가 들어있는 클래스 뿐만 아니라 그 클래스가 상속하는 부모 클래스의 인스턴스이기도 하고, 그 부모 클래스가 상속하는 조부모 클래스이기도 하다는 것입니다. 이 것은 자식 클래스의 생성자는 반드시 부모 클래스의 생성자를 (명시적으로든, 암묵적으로든) 호출하기 때문인데요. 그러므로 우리는 해당 인스턴스는 자식 클래스의 생성자 뿐만 아니라 부모 클래스의 (또는, 그 부모의 부모 클래스의) 생성자에 의해서 만들어졌다고 볼 수 있는 것죠.

예를 들어서, 다음과 같이 Person 클래스와 Person 클래스를 상속하는 Student 클래스를 가지고 instanceof 연산자를 테스트해보겠습니다.

class Person {
  constructor() {
    /* ... */
  }
}
class Student extends Person {
  constructor() {
    super();
    /* ... */
  }
}

먼저 Person() 생성자로 만들어낸 객체는 Student 클래스의 인스턴스는 아니지만, Person 클래스의 인스턴스입니다. 그리고 자바스크립트에서 모든 클래스는 Object 클래스는 상속하기 때문에 Object 클래스의 인스턴스이기도 합니다.

const person = new Person();
person instanceof Student; // false
person instanceof Person; // true
person instanceof Object; // true

반면에 Student() 생성자로 만들어낸 객체는 Student 클래스의 인스턴스이기도 하지만, Student 클래스의 부모인 Person 클래스의 인스턴스이기도 합니다. 그리고 Person 클래스의 부모가 Object 클래스이므로 당연히 Object 클래스의 인스턴스이기도 합니다.

const student = new Student();
student instanceof Student; // true
student instanceof Person; // true
student instanceof Object; // true

이렇게 instanceof 연산자가 해당 객체를 생성한 클래스 뿐만 아니라, 그 클래스가 상속하고 있는 부모 클래스를 상대로도 참을 반환하는 것은 자바스크립트에 내장된 클래스에서도 쉽게 찾아 볼 수 있죠.

예를 들어, Date 클래스는 Object 클래스를 상속하기 때문에, Date() 생성자로 만든 객체는 두 클래스 모두의 인스턴스로 판별됩니다.

const date = new Date();
date instanceof Date; // true
date instanceof Object; // true

프로토타입 체인

자바스크립트에서 객체 간의 상속 관계는 프로토타입 체인(prototype chain)이라는 독특한 내부 매커니즘을 통해 형성이 됩니다. 그리고 이 부분은 instanceof 연산자의 작동 원리는 이해하는데 매우 중요한데요. 왜냐하면 instanceof 연산자는 결국 객체의 프로토타입 체인 상에 주어진 클래스가 존재하는지를 확인하기 때문입니다.

기본적으로 클래스의 인스턴스를 생성하면 해당 객체의 프로토타입은 클래스의 생성자의 prototype 속성이 되는데요. 생성자의 prototype 속성에는 클래스의 메서드가 정의되어 있기 때문에 해당 객체는 프로토타입 체인을 통해서 정의된 메서드를 호출할 수 있게 됩니다.

예를 들어, 위에서 작성한 Student 클래스의 인스턴스를 생성한 후, Object.getPrototypeOf() 메서드로 프로토타입을 확인해보면, Student() 생성자의 prototype 속성과 동일하다는 것을 볼 수 있습니다.

const student = new Student();
Object.getPrototypeOf(student) === Student.prototype; // true

Student 클래스의 인스턴스를 상대로 Object.getPrototypeOf() 메서드를 중첩 호출하면 프로토타입의 프로토타입을 확인해볼 수 있는데, Person() 생성자의 prototype 속성과 동일하다는 것을 알 수 있습니다.

Object.getPrototypeOf(Object.getPrototypeOf(student)) === Person.prototype; // true

마찬가지로 한 단계 더 거슬러 올라가면 Object() 생성자의 prototype 속성까지 도달할 수 있습니다.

Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(student))) ===
  Object.prototype; // true

instanceof 연산자는 객체의 프로토타입을 타고 올라가면서 주어진 클래스를 탐색합니다. 만약에 클래스가 발견되면 결과로 참을 주고, 프로토타입의 끝에 도달하여 null을 만나면 거짓을 준다고 생각하시면 되겠습니다.

객체의 프로토타입에 접근하기 위해서 사용한 Object.getPrototypeOf()에 대해서는 별도 포스팅에서 자세히 다루고 있으니 참고 바랍니다.

클래스의 생성자로 만들지 않는 객체는?

Object.create() 메서드를 사용해서 생성한 객체를 상대로도 instanceof 연산자를 사용할 수 있습니다. 클래스의 생성자에 new 키워드를 붙여서 객체를 생성했을 때와 동일한 결과를 얻을 수 있습니다.

const student = Object.create(Student.prototype);
student instanceof Student; // true
student instanceof Person; // true
student instanceof Object; // true

배열 리터럴(literal)이나 객체 리터럴의 경우에도, 각각 Array 클래스와 Object 클래스의 인스턴스이기 때문에 instanceof 연산자를 사용하는데 아무 문제가 없습니다.

const arr = [];
arr instanceof Array; // true
arr instanceof Object; // true
const obj = {};
student instanceof Object; // true

객체를 직접 생성한 클래스만 알고 싶다면?

instanceof 연산자는 객체를 생성한 클래스 뿐만 아니라 그 클래스의 모든 조상 클래스를 상대로도 참을 반환한다고 배웠는데요. 만약에 조상 클래스를 제외하고 해당 객체를 직접적으로 생성한 클래스인지를 판별하려면 어떻게 해야할가요?

이럴 때는 Object.getPrototypeOf() 메서드를 통해서 해당 객체의 프로토타입이 무엇인지를 확인해보면 됩니다.

예를 들어서, 위에서 작성한 Student 클래스의 인스턴스를 상대로 테스트를 해보면, 프로토타입이 Student.prototype과는 일치하지만, Person.prototype과는 일치하지 않는 것을 알 수 있습니다.

const student = new Student();
Object.getPrototypeOf(student) === Student.prototype; // true
Object.getPrototypeOf(student) === Person.prototype; // false

클래스의 인스턴스가 아닌지 확인할 때 주의사항

객체가 어떤 클래스의 인스턴스가 아닌지를 확인할 때 코딩하면서 실수하기가 쉬운 부분이 있는데요.

객체 앞에 바로 !기호를 붙이면 instanceof 연산자의 결과는 항상 거짓이 됩니다. 왜냐하면 !객체 부분이 먼저 실행되어 false가 되기 때문에 결국 false instanceof 클래스가 되기 때문입니다. false는 원시(primitive) 자료형이라서 어떤 클래스의 인스턴스도 아닙니다.

const person = new Person();
!person instanceof Student; // false

따라서 반드시 객체 instanceof 클래스 부분을 먼저 괄호로 감싼 후에 그 앞에 ! 기호를 붙여줘야겠습니다.

const person = new Person();
!(person instanceof Student); // true

마치면서

이상으로 객체가 클래스의 인스턴스인지 알아내기 위해서 사용하는 instanceof 연산자에 대해서 살펴보고, instanceof 연산자의 동작 방식을 이해하기 위해서 자바스크립트의 프로토타입 체인에 대해서도 배웠습니다.

자바스크립트를 공부하시는 많은 분들이 instanceof 연산자와 typeof 연산자를 혼동하십니다. typeof 연산자에 대해서는 별도의 포스팅에서 자세히 다루고 있으니 참고 바랍니다.