개인공부/TIL(Today I Learned)

TIL 41일차_상속과 프로토타입

soon327 2021. 2. 28. 03:46

ES6부터 class키워드를 지원하기 시작했으나, 문법적인 양념일뿐이며
자바스크립트는 여전히 프로토타입 기반의 언어이다.

상속 관점에서 자바스크립트의 유일한 생성자는 객체뿐이다.
각각의 객체는 [[Prototype]]이라는 은닉(private) 속성을 가지는데 자신의 프로토타입이 되는 다른 객체를 가리킨다.
그 객체의 프로토타입 또한 프로토타입을 가지고 있고 이것이 반복되다,
결국 null을 프로토타입으로 가지는 오브젝트에서 끝난다.
null은 더 이상의 프로토타입이 없다고 정의되며, 프로토타입 체인의 종점 역할을 한다.

속성 상속

자바스크립트 객체는 속성을 저장하는 동적인 "가방"과 (자기만의 속성이라고 부른다) 프로토타입 객체에 대한 링크를 가진다.
객체의 어떤 속성에 접근하려할 때 그 객체 자체 속성 뿐만 아니라 객체의 프로토타입, 그 프로토타입의 프로토타입 등
프로토타입 체인의 종단에 이를 때까지 그 속성을 탐색한다.

// o라는 객체가 있고, 속성 'a' 와 'b'를 갖고 있다고 하자.
let f = function () {
    this.a = 1;
    this.b = 2;
}
let o = new f(); // {a: 1, b: 2}

// f 함수의 prototype 속성 값들을 추가 하자.
f.prototype.b = 3;
f.prototype.c = 4;

// f.prototype = {b: 3, c: 4}; 라고 하지 마라, 해당 코드는 prototype chain 을 망가뜨린다.
// o.[[Prototype]]은 속성 'b'와 'c'를 가지고 있다.
// o.[[Prototype]].[[Prototype]] 은 Object.prototype 이다.
// 마지막으로 o.[[Prototype]].[[Prototype]].[[Prototype]]은 null이다.
// null은 프로토타입의 종단을 말하며 정의에 의해서 추가 [[Prototype]]은 없다.
// {a: 1, b: 2} ---> {b: 3, c: 4} ---> Object.prototype ---> null

console.log(o.a); // 1
// o는 'a'라는 속성을 가지는가? 그렇다. 속성의 값은 1이다.

console.log(o.b); // 2
// o는 'b'라는 속성을 가지는가? 그렇다. 속성의 값은 2이다.
// 프로토타입 역시 'b'라는 속성을 가지지만 이 값은 쓰이지 않는다. 이것을 "속성의 가려짐(property shadowing)" 이라고 부른다.

console.log(o.c); // 4
// o는 'c'라는 속성을 가지는가? 아니다. 프로토타입을 확인해보자.
// o.[[Prototype]]은 'c'라는 속성을 가지는가? 가지고 값은 4이다.

console.log(o.d); // undefined
// o는 'd'라는 속성을 가지는가? 아니다. 프로토타입을 확인해보자.
// o.[[Prototype]]은 'd'라는 속성을 가지는가? 아니다. 다시 프로토타입을 확인해보자.
// o.[[Prototype]].[[Prototype]]은 null이다. 찾는 것을 그만두자.
// 속성이 발견되지 않았기 때문에 undefined를 반환한다.

메소드 상속

상속된 함수가 실행 될 때, this 라는 변수는 상속된 오브젝트를 가르킨다.
그 함수가 프로토타입의 속성으로 지정되었다고 해도 말이다.

var o = {
  a: 2,
  m: function(b){
    return this.a + 1;
  }
};

console.log(o.m()); // 3
// o.m을 호출하면 'this' 는 o를 가리킨다.

var p = Object.create(o);
// p 는 프로토타입을 o로 가지는 오브젝트이다.

p.a = 12; // p 에 'a'라는 새로운 속성을 만들었다.
console.log(p.m()); // 13
// p.m이 호출 될 때 'this' 는 'p'를 가리킨다.
// 따라서 o의 함수 m을 상속 받으며,
// 'this.a'는 p.a를 나타내며 p의 개인 속성 'a'가 된다.

__proto__와 prototype chain

function doSomething(){}
doSomething.prototype.foo = "bar"; // add a property onto the prototype
var doSomeInstancing = new doSomething();
doSomeInstancing.prop = "some value"; // add a property onto the object
console.log( doSomeInstancing );

콘솔 실행결과는 다음과 같다.

위의 사진을 보면 doSomeInstancing 객체의 __proto__ 는 doSomething.prototype 이다.
__proto__ 는 뭘까??

우리가 doSomeInstancing의 속성에 접근할때 브라우저는 우선 doSomeInstancing이 그 속성을 갖고있는지 확인한다.
만약 doSomeInstancing이 속성을 갖고있지 않다면,
브라우저는 doSomeInstancing의 __proto__(doSomething.prototype)가 그 속성을 갖고있는지 확인한다.

만약 doSomeInstancing의 __proto__가 브라우저가 찾던 속성을 갖고 있다면,
doSomething의 __proto__가 갖고있는 그 속성을 사용한다.

그렇지 않고, doSomeInstancing의 __proto__가 그 속성을 갖고있지 않을때에는
doSomeInstancing의 __proto__의 __proto__가 그 속성을 갖는지 확인한다.

기본적으로, 어떠한 함수던지 그 함수의 prototype 속성의 __proto__는 window.Object.prototype이다.
그러므로 브라우저는 doSomeInstancing의 __proto__의 __proto__(doSomething.prototype의 __proto__(다시말해, Object.prototype)) 에서 그 속성을 찾아본다.

만약 그 속성을 doSomeInstancing의 __proto__의 __proto__에서 찾을 수 없다면 그다음엔 doSomeInstancing의 __proto__의 __proto__의 __proto__에서 찾을것이다.

하지만 여기서 문제가 발생한다.
doSomeInstancing의 __proto__의 __proto__의 __proto__는 존재할 수 없다
(window.Object.prototype의 __proto__는 null이기 때문).

그제서야, 오직 모든 프로토타입 체인이 검사 되고 브라우저가 더이상 검사할 __proto__가 없을때에서야 브라우저는 우리가 찾던 값이 undefined라고 결론짓는다.

참고: MDN 상속과 프로토타입