프로토타입 상속

100pearlcent·2021년 9월 20일
1

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.getPrototypeOfObject.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) > animalrabbit의 프로토타입이 되도록 설정
(2) > alert함수가 rabbit.eats 프로퍼티를 읽으려 했으나 rabbiteats라는 프로퍼티가 없다.


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

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

이렇게 프로토타입에서 상속받은 프로퍼티를 상속 프로퍼티(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(); // 토끼가 뛰어요

위 처럼 객체 rabbitwalk메서드를 직접 할당한 후 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.namethis.surname에 값을 쓰면 그 값은 user에 저장될까 아니면 admin에 저장될까?

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

admin.fullName으로 setter 함수를 호출하면 thisuser가 아닌 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개의 댓글