프로토타입 상속

100pearlcent·2021년 9월 20일

JavaScript

목록 보기
14/22
post-thumbnail

[[prototype]]

자바스크립트의 객체는 [[prototype]]이라는 숨김 프로퍼티를 가진다
이 숨김 프로퍼티 값은 null이거나 다른 객체에 대한 참조가 되는데, 다른 객체를 참조하는 경우 참조 대상을 '프로토타입(prototype)'이라 부른다


프로토타입 상속이란

object에서 프로퍼티를 읽으려 할 때 해당 프로퍼티가 없으면 자동으로 프로토타입에서 프로퍼티를 찾는 동작 방식

[[Prototype]] 프로퍼티는 내부 프로퍼티이면서 숨김 프로퍼티이지만 다양한 방법을 사용해 개발자가 값을 설정할 수 있다

let animal = {
	eats: true
};

let rabbit = {
	jumps: true
};

rabbit.__proto__ = animal;

proto

👉[[prototype]]의 getter이자 setter

__proto__ != [[Prototype]]

하위 호환성 때문에 여전히 __proto__를 사용할 순 있지만 비교적 근래에 작성된 스크립트에서는 __proto__대신 Object.getPrototypeOf나 Object.setPrototypeOf를 써서 프로토타입을 get하거나 set한다

__proto__는 브라우저 환경에서만 지원하도록 자바스크립트 명세서에 규정되었으나 실상은 서버 사이드를 포함한 모든 호스트 환경에서 __proto__를 지원한다

let animal = {
	eats: true
};

let rabbit = {
	jumps: true
};

rabbit.__proto__ = animal; // (1)

alert(rabbit.eats); // true, (2)
alert(rabbit.jumps); // true

(1) > animal이 rabbit의 프로토타입이 되도록 설정
(2) > alert함수가 rabbit.eats 프로퍼티를 읽으려 했으나 rabbit엔 eats라는 프로퍼티가 없다.


이 때 [[Prototype]]이 참조하는 객체인 animal에서 eats를 얻어낸다

👉 rabbit의 프로토타입은 animal이다
👉 rabbit은 animal을 상속 받는다

이렇게 프로토타입에서 상속받은 프로퍼티를 상속 프로퍼티(inherited property)라고 한다

let animal = {
	eats: true,
  	walk() { alert('동물이 걸어요'); }
};

let rabbit = {
	jumps: true,
  	__proto__: animal
};

// rabbit의 프로토타입인 animal에서 상속받은 메서드 walk
rabbit.walk(); // 동물이 걸어요

프로토타입 체이닝

let animal = {
  eats: true,
  walk() {
    alert('동물이 걸어요');
  }
};

let rabbit = {
  jumps: true,
  __proto__: animal
};

let longEar = {
  earLength: 10,
  __proto__: rabbit
};

// 프로토타입 체인을 통해 상속받은 메서드 walk
longEar.walk(); // 동물이 걸어요
alert(longEar.jumps); // true (rabbit에서 상속받음)

⚠️ 프로토타입 체이닝의 제약사항

  • 순환 참조(circular reference)는 허용되지 않는다 = __proto__를 이용해 닫힌 형태로 다른 객체를 참조하면 에러가 발생
  • __proto__의 값은 객체나 null만 가능하고 다른 자료형은 무시된다
  • 객체에는 오직 하나의 [[Prototype]]만 있을 수 있다 = 객체는 두 개의 객체를 상속받지 못한다

프로퍼티를 읽을 때만 프로토타입을 사용한다

프로퍼티를 추가, 수정하거나 지우는 연산은 객체에 직접 해야한다

let animal = {
	eats: true,
  	walk() {} // rabbit은 이제 이 메서드를 사용하지 않음
};

let rabbit = {
	__proto__: animal
};

rabbit.walk = function() {
	alert('토끼가 뛰어요');
};

rabbit.walk(); // 토끼가 뛰어요

위 처럼 객체 rabbit에 walk메서드를 직접 할당한 후 rabbit.walk()를 호출하면 프로토타입의 메서드가 실행되지 않고 객체 rabbit에 추가한 메서드가 실행된다


❗하지만 접근자 프로퍼티는 setter 함수를 통해 프로퍼티에 값을 할당하므로 이 규칙이 적용되지 않는다
❓접근자 프로퍼티에 값을 할당하는 것은 함수를 호출하는 것과 같기 때문이다
let user = {
	name: 'Jinju',
  	surname: 'Baek',
  
  set fullName(value) {
  	[this.name, this.surname] = value.split(' ');
  },
  
  get fullName() {
  	return `${this.name} ${this.surname}`;
  }
};

let admin = {
	__proto__: user;
  	isAdmin: true
};

alert(admin.fullName); // Jinju Baek (1)

// setter 함수가 실행되는 부분
admin.fullName = 'John Doe'; // (2)

alert(admin.fullName); // John doe
alert(user.fullName); // Jinju Baek

(1) > admin.fullName은 프로토타입 user의 getter 함수를 호출
(2) > 프로토타입의 setter 함수를 호출


'this'가 나타내는 것

setter 함수의 this에 어떤 값이 들어갈까?
프로퍼티 this.name과 this.surname에 값을 쓰면 그 값은 user에 저장될까 아니면 admin에 저장될까?

👉 this는 프로토타입에 영향을 받지 않는다
메서드를 객체에서 호출했든 프로토타입에서 호출했든 상관없이 this는 . 앞에 있는 객체가 된다.

admin.fullName으로 setter 함수를 호출하면 this는 user가 아닌 admin이 된다

for..in 반복문

for..in은 상속 프로퍼티도 순회대상에 포함시킨다

let animal = {
	eats: true
};

let rabbit = {
	jumps: true,
  	__proto__ : animal
};

// Object.keys는 객체 자신의 key만 반환한다
alert(Object.keys(rabbit)); // jumps

// for..in은 객체 자신의 key와 상속 프로퍼티 key 모두를 순회
for(let prop in rabbit) alert(prop); // jumps, eats

❗key-value를 순회하는 메서드 대부분은 상속 프로퍼티를 제외하고 동작한다
Object.keys, Object.values같이 객체의 key-value를 대상으로 무언가를 하는 메서드 대부분은 상속 프로퍼티를 제외하고 동작한다
프로토타입에서 상속받은 프로퍼티는 제외하고, 해당 객체에서 정의한 프로퍼티만 연산 대상에 포함한다


프로토타입 상속 요약

  • JavaScript의 모든 객체엔 숨김 프로퍼티 [[Prototype]]이 있는데, 이 프로퍼티는 객체나 null을 가리킨다
  • obj.__proto__를 사용하면 프로토타입에 접근할 수 있다
  • __proto__는 [[Prototype]]의 getter와 setter로 쓰이는데 요즘엔 잘 쓰이지 않는다
  • obj에서 프로퍼티를 읽거나 메서드를 호출하려는데 해당하는 프로퍼티나 메서드가 없으면 자바스크립트는 프로토타입에서 프로퍼티나 메서드를 찾는다
  • 접근자 프로퍼티가 아닌 데이터 프로퍼티를 다루고 있다면, 쓰기나 지우기와 관련된 연산은 프로토타입을 통하지 않고 객체에 직접 적용된다
  • 프로토타입에서 상속받은 method라도 obj.method()를 호출하면 method안의 this는 호출 대상 객체인 obj를 가리킨다
  • for..in 반복문은 객체 자체에서 정의한 프로퍼티뿐만 아니라 상속 프로퍼티도 순회 대상에 포함한다

0개의 댓글