[Javascript] 프로토타입 총정리 ② | 프로토타입의 체이닝과 오버라이딩, 프로퍼티 섀도잉

Re_Go·2023년 12월 17일
0

Javascript

목록 보기
20/44
post-thumbnail

1. 프로토타입 체이닝

자바스크립트는 객체의 프로퍼티에 접근하려고 할 때 객체에 접근하려는 프로퍼티가 없을 경우, [[Prototype]] 내부 슬롯의 참조를 따라 자신의 부모 역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색하는데 이를 프로토타입 체인이라고 하며 이는 자바스크립트의 객체지향 프로그래밍의 상속을 구현하는 메커니즘이기도 합니다.

그리고 이러한 프로토타입 체이닝에 입각해 객체가 특정 프로퍼티나 메서드를 찾는 과정은 다음과 같습니다.

  1. 먼저, 인스턴스 내부에서 해당 메서드 또는 프로퍼티를 찾습니다. 즉, 인스턴스가 직접 해당 메서드 또는 프로퍼티를 가지고 있는지 확인합니다.
  2. 인스턴스 내에서 찾지 못한 경우, 해당 생성자 함수의 프로토타입 객체를 확인합니다. 생성자 함수의 프로토타입 객체에 해당 메서드 또는 프로퍼티가 있는지 확인합니다.
  3. 생성자 함수의 프로토타입 객체에서도 찾지 못한 경우, 프로토타입 체인을 따라 상위 프로토타입 객체를 확인합니다. 이 과정에서 생성자 함수의 프로토타입이 가리키고 있는 다음 상위 프로토타입 객체인 Object.prototype 객체에서 검색을 시작합니다.
  4. Object.prototype에서 확인 가능한 상속 프로퍼티 및 메서드일 경우 서칭을 종료하거나, 해당 프로토타입 객체에서도 확인이 불가능한 경우 undefined를 반환합니다.

만약 이러한 프로토타입 체이닝 상에서 해당 인스턴스가 생성자 함수의 프로토타입과 연결 되었는지에 대해 확인해보려면 instanceof 연산자로 확인이 가능함을 알아두시면 좋습니다.

function CustomConstructor() {
  this.property = "value";
}

const instance = new CustomConstructor();

// instance가 CustomConstructor의 프로토타입 체인에 속하는지 확인
console.log(instance instanceof CustomConstructor); // true

한편 객체의 프로퍼티나 메서드를 검색하는 것이 아니라 단순히 식별자를 검색할 경우 프로토타입 체이닝이 아닌 스코프 체이닝은 입각하여 검색하므로 이 체이닝은 식별자가 선언된 위치가 함수 레벨 스코프 지역인지, 블록 레벨 스코프 지역인지에 따라 검색 위치와 순서의 방향을 정하게 된다는 점을 알아두시면 좋은데, 이러한 스코프 체이닝과 프로토타입 체이닝은 서료 연관없이 별도로 동작하는 것이 아니라 서로 협력하여 식별자와 프로퍼티를 검색하는데 사용된다는 점을 알아두시는게 핵심입니다.

2. 오버라이딩과 프로퍼티 섀도잉

이런 상상 해보신 적이 있나요?

"만약 생성자 함수의 프로토타입 객체에서 소유한 메서드의 이름으로 인스턴스의 메서드를 같은 이름으로 생성한다면 어떻게 될까?"

생성자 함수의 프로토타입에서 선언되어있는 메서드의 이름으로 인스턴스에서 똑같은 이름의 메서드를 선언하게 된다면 인스턴스가 해당 메서드를 호출할 때 프로토타입 체이닝에 의해서 프로토타입 객체에서 상속을 받는 메서드가 아니라, 인스턴스가 소유한 메서드(프로토타입 객체의 이름과 똑같은 이름으로 생성된 인스턴스의 메서드)를 검색하게 됩니다.

즉 개발자가 상속을 받고 있는 메서드를 호출하고 싶은데 인스턴스가 소유하고 있는 메서드를 호출하게 되는 그림이 그려지는데요.

이처럼 인스턴스의 메서드가 상속 받는 메서드를 가려버리는 현상을 오버라이딩(게임에서 내가 트롤을 하는 것과 같습니다.) 이라고 하고, 이때 프로토타입에서 인스턴스에게 상속을 주어야 할 메서드가 가려지는 현상을 프로퍼티 섀도잉 이라고 불리우는 것이죠.

정리하자면 인스턴스가 상속 받는 프로퍼티나 메서드를 가려버리면 오버라이딩, 프로토타입 프로퍼티나 메서드가 가려질 경우에는 프로퍼티 섀도잉으로 정리할 수 있겠습니다.

이러한 현상을 막아주기 위해서는 무엇보다 인스턴스에서 상속 받는 메서드와 같은 이름으로 메서드를 재선언 하는 행위를 지양하는게 좋지만, 만들게 되었을 경우에는 delete로 깔끔하게 지우고 다른 이름으로 메서드를 다시 생성하면 되는데요.

이때 알아두면 좋은 점은 인스턴스의 메서드를 삭제 후 다시 한번더 같은 메서드 이름으로 delete를 호출할 경우 프로토타입 객체의 메서드가 삭제되지 않는다는 것입니다. 이는 인스턴스가 상속을 받는 행위(get)는 할 수 있어도 그 상속값을 처리하는 행위(set)는 할 수 없음을 의미하기도 합니다.


function Person(name, age) {// Person 생성자 함수 정의
    this.name = name;
  	this.age = age;
}
 
Person.prototype.sayHello = function() { //Person 생성자 함수의 프로토타입 객체에 sayHello 함수 정의
    console.log(`Hi! My name is ${this.name} and age is ${this.age}!`);
};

const me = new Person('Lee'); // Person 생성자 함수로 me 인스턴스를 생성

me.sayHello = function(){ // me 인스턴스에 프로토타입으로 상속 받는 메서드 sayHello와 동일한 이름으로 생성
    console.log(`Hi! My name is ${this.name}`);
}

me.sayHello(); // 이 경우 인스턴스에서 상속 받는 메서드가 아니라 인스턴스에서 직접 생성한 메서드가 호출됩니다.

delete me.sayHello(); // 이러한 인스턴스의 메서드를 지우고 싶다면 delete 연산자로 인스턴스가 가지고 있는 메서드를 삭제하면 오버라이딩과 섀도잉 현상을 해결할 수 있습니다.

delete me.sayHello(); // 만약 사용자가 프로토타입 메서드까지 삭제하려고 delete를 다시 호출했다면 이는 무효화 됩니다. 값을 읽어올 수는 있지만 수정할 수는 없기 때문입니다.

★ 오버라이딩과 비슷한 용어로 오버로딩이 있는데, 오버로딩은 같은 이름으로 선언된 함수 및 메서드가 여러개 선언되어도 다른 타입 및 개수의 매개변수를 받도록 각각 지정을 한다면, 함수의 이름이 같아도 함수 호출 시 매개변수의 상태에 따라 정해진 함수가 선택된다는 의미이므로 이 두 용어를 구분하는 것 또한 필요합니다.

profile
인생은 본인의 삶을 곱씹어보는 R과 타인의 삶을 배워 나아가는 L의 연속이다.

0개의 댓글