본문 바로가기
JavaScript

[JavaScript] Prototype Chain/직접 상속

by 배잼 2022. 2. 17.

모던 자바스크립트 Deep dive 19장. 나중에 읽어볼 기회가 있다면 꼭 그림을 같이 보며 확인하자.

프로토타입 체인

자바스크립트는 객체의 property/method에 접근하려고 할 때 해당 객체에 접근하려는 property가 있는지 체크한다. 만약 없다면, [[ prototype ]] 내부 슬롯의 참조를 따라 자신의 부모 역할을 하는 prototype의 property를 순차적으로 검색한다. 이를 prototype chain이라고 한다 → JS가 객체지향 프로그래밍의 상속을 구현하는 매커니즘

end of prototype chain : 프로토타입 체인의 최상위에 위치하는 객체는 언제나 Object.prototype다. 따라서 모든 객체는 Object.prototype를 상속받는다. Object.prototype의 prototype는 null이다.

Shadowing, Overriding

Overriding : 상위 클래스가 가지고 있는 method를 하위 클래스가 재정의해서 사용하는 방식

property Shadowing : 상속 관계에 의해 property가 가려지는 현상

예제를 보도록 하자.

const Person = (function(){

    function Person(name){
        this.name = name;
    }

    Person.prototype.sayHello = function() {
        console.log(`Hi, My name is ${this.name}`);
    };

    return Person;
}());

const me = new Person('Kim');

me.sayHello = function (){
    console.log(`Hey, My name is ${this.name}`);
};

me.sayHello(); // Hey, My name is Kim

분명히! prototype에서는 hi, 라고 인사하지만 정작 출력은 hey다. prototype property와 같은 이름의 property를 instance에 추가하면 instance property로 추가된다. (덮어쓰는 게 아니다.)

→ instance method sayHello는 prototype method sayHello를 overriding했다.

→ prototype method sayHello는 가려진다 : property shadowing

만약 method를 삭제해볼까? 그렇다면 instance method가 삭제되므로, hi,가 출력된다.

만약 다시 한번 한다 해도, 하위 객체를 통해 prototype property를 변경할 수 없다 → 변경하려면 prototype에 직접 접근하자.

프로토타입은 임의의 다른 객체로 변경할 수 있다

객체 간의 상속 관계를 동적으로 변경할 수 있다. 생성자 함수 또는 인스턴스에 의해 교체 가능하다.

만약 교체한다면 constructor property와 생성자 함수 간의 연결이 파괴된다. → constructor property는 JS 엔진이 protoytpe을 생성할 때 암묵적으로 추가한 거니까. 따라서 Object.prototype로 연결된다...

연결 되살리기 : 간단하다! constructor property를 추가해주면 된다.

  • 생성자 함수의 prototype property를 통해 다른 임의의 객체 바인딩 : 미래에 생성할 인스턴스의 프로토타입을 교체
  • __proto__ 접근자 property를 통해 prototype를 교체하는 것은 이미 생성된 객체의 prototype을 교체하는 셈.

두 차이는 무엇일까? 바로 생성자 함수의 protorype property가 교체된 프로토타입을 가리키는지, 혹은 그렇지 않은지다. 인스턴스를 활용하면 가리키지 않는다. → 이를 되살리려면 마찬가지로 constructor property를 추가하고, 생성자 함수의 prototype property를 재설정해야한다.

⇒ 귀찮다! 더 좋은 방법이 있다 🙂 : 직접 상속

instanceof 연산자

보이는 대로다. 그러나 인스턴스인지 평가하는 게 아니라, 프로토타입 체인을 평가한다.

문법은 객체 instanceof 생성자 함수 다. 우변의 생성자 함수의 prototype에 바인딩 된 객체가 좌변의 객체의 프로토타입 체인 상에 존재하면 true, 그렇지 않으면 false다.

즉, 만약 A 생성자 함수로 생성된 인스턴스더라도 프로토타입이 교체되어 연결이 파괴되면 프로토타입 체인 상에 존재하지 않는다. 따라서 false가 반환되고는 한다. → 반대로 연결이 파괴되지 않으면 true를 반환할 것이다.

직접 상속

Object.create method

  • 첫 번째 매개변수는 생성할 객체의 프로토타입으로 지정할 객체를 전달한다.
  • 두 번째 매개변수는 생성할 객체의 property key와 discripter 객체로 이뤄진 객체를 전달한다(option)

따라서 명시적으로 프로토타입을 지정해서 새로운 객체를 생성할 수 있다.

  • 만약 null을 전달한다면 ? → 생성된 객체는 프로토타입 체인의 종점에 위치하게 된다.
const myProto = { x : 10};

let obj = Object.create(myProto);
//obj -> myProto -> Object.prototype -> null

장점은 다음과 같다.

  • new 연산자가 없어도 객체를 생성할 수 있다.
  • 프로토타입을 지정하면서 객체를 생성할 수 있다.
  • 객체 리터럴에 의해 생성된 객체도 상속받을 수 있다.

그러나 두 번째 인자로 property를 정의하는 건 번거로운 일이다. → __proto__ 접근자 property를 사용해 보자.

const myProto = { x : 10};

const obj = {
    y : 20,
    __proto__ : myProto
};

/*
다음과 동일하다.
const obj = Object.create(myProto,{
    y : {value : 20, writable: true, enumerable:true, configurable:true}
})
*/

Static Property/Method

생성자 함수로 인스턴스를 생성하지 않아도 참조/호출 가능한 property, method다. 왜냐하면 생성자 함수도 객체기 때문이다. 이는 생성자 함수가 생성한 인스턴스로 참조/호출이 불가능하다. 왜냐하면 프로토타입 체인에 속해 있는게 아니기 때문이다.

  • 즉, Object.create 는 Object 생성자 함수의 static method다. → Object 생성자 함수가 생성한 객체로 호출 불가능
  • Object.prototype.hasOwnProperty 메서드는 Object.prototype의 method다 → 모든 객체가 호출할 수 있다!

인스턴스/프로토타입 메서드 내에서 this를 사용하지 않는다면 그 method는 정적 메서드로 변환할 수 있다.

property 존재의 확인

  1. in 연산자 : 주의, 확인 대상 객체가 상속받은 모든 prototype의 property를 확인하므로 주의.
  2. Object.prototype.hasOwnProperty : 인수로 전달받은 프로퍼티 키가 객체 고유의 프로퍼티 키일 경우에만 true다.

property 열거

  1. for (변수선언문 in 객체) { } : 상속받은 프로토타입의 property까지 열거하지만, Object.prototype의 property는 열거하지 않는다, 이는 Enumerable의 값이 false라서. 프로퍼티 키가 심벌이어도 열거하지 않는다. 또, 순서를 보장하지 않는다.→ 만약 객체 자신의 것만 하려면 Object.prototype.hasOwnProperty 을 사용하자.
  2. Object.keys/values/entries : 객체 자신의 enumerable한 property keys/values/entries를 배열로 반환한다.

댓글