프로토타입과 상속

345·2023년 6월 27일

모던 JavaScript

목록 보기
19/23

✅ 프로토타입

자바스크립트의 객체는 프로토타입을 상속하여 기능을 확장합니다.
객체가 가지는 [[Prototype]] 이라는 숨김 프로퍼티는 다른 객체에 대한 참조나 null 값을 갖습니다.

[[Prototype]] 이 다른 객체를 참조할 때, 그 참조 대상을 프로토타입 이라고 합니다.


✨ 프로토타입 상속

프로토타입을 지정하지 않아도 객체 리터럴 문법으로 객체를 생성하면 [[Prototype]]Object 인 걸 확인할 수 있습니다.
객체는 자동으로 Object.prototype 을 상속받아 기능을 사용합니다.

프로토타입을 따로 지정하면, 상속 받은 객체는 프로토타입의
프로퍼티와 메서드를 사용할 수 있습니다.

객체는 자신에게 없는 프로퍼티를 참조하면, 프로토타입을 탐색하여 찾습니다.
프로토타입에는 다음과 같은 특징이 있습니다.

  1. __proto__ 로 프로토타입 지정
  2. __proto__[[Prototype]] 을 위한 getter-setter
  3. __proto__ 에는 null 이나 객체만 올 수 있음(다른 값은 무시)
  4. 오직 하나의 프로토타입만 상속 가능! 두 개 이상은 ❌
    (별개로, 상속 체이닝 가능)
  5. __proto__ 로 프로토타입 객체의 속성을 변경할 수는 ❌
    읽기만 가능
    (하위 객체를 사용하여 상위 객체를 수정할 수 없음)

다음과 같이, __proto__ 속성에 프로토타입으로 사용할 객체를 지정해줌으로서 프로토타입의 기능을 사용할 수 있습니다.

let animal = {
  eats: true
};
let rabbit = {
  jumps: true
  // 아니면 여기에 직접
  // __proto__: animal
};

rabbit.__proto__ = animal; // (*)

// 프로퍼티 eats과 jumps를 rabbit에서도 사용
alert( rabbit.eats ); // true (**)
alert( rabbit.jumps ); // true

상속 체이닝

rabbit 객체는 animal 객체를 상속받고, animalObject.prototype 을 상속받고 있습니다.

rabbit.hasOwnProperty(key) 를 사용하면 다음과 같은 순서로 탐색합니다.

  1. rabbit 자기 자신 탐색
  2. 없으면 프로토타입인 animal 탐색
  3. 없으면 프로토타입인 Object 탐색

이처럼... 상속 체인을 올라가며 탐색합니다. 같은 이름의 프로퍼티가 존재하면 가장 가까운 위치의 프로퍼티를 사용하게 됩니다.


❗ this 가 가리키는 대상

this 는 어디서 호출하든 언제나 . 앞의 객체를 가리킵니다.
상속 받은 프로토타입의 메서드에서 this 를 사용해도, 이는 프로토타입이 아니라 메서드를 호출한 객체 자기 자신을 가리킵니다.

let animal = {
  walk() {
    if (!this.isSleeping) {
      alert(`동물이 걸어갑니다.`);
    }
  },
  sleep() {
    this.isSleeping = true;
  }
};

let rabbit = {
  name: "하얀 토끼",
  __proto__: animal
};

// rabbit에 새로운 프로퍼티 isSleeping을 추가하고 그 값을 true로 변경
rabbit.sleep();

alert(rabbit.isSleeping); // true
alert(animal.isSleeping); // undefined (프로토타입에는 isSleeping이라는 프로퍼티가 없음)

메서드는 공유하여 사용할 수 있지만 객체의 상태는 공유하지 않습니다.


for..in 에는 상속 프로퍼티도 포함

for..in 을 이용해서 객체의 프로퍼티를 순회할 때, 객체 자신의 프로퍼티와 상속 프로퍼티 모두를 포함합니다.

이를 필터링하기 위해, obj.hasOwnProperty(key) 를 사용합니다.

obj.hasOwnProperty(key);
  • obj: 객체
  • key: 프로퍼티 키
  • 반환값: 객체 자신이 해당 프로퍼티를 가지면 true, 아니면(없거나, 프로토타입에만 있거나) false
let animal = {
  eats: true
};

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

for(let prop in rabbit) {
  let isOwn = rabbit.hasOwnProperty(prop);

  if (isOwn) {
    alert(`객체 자신의 프로퍼티: ${prop}`); // 객체 자신의 프로퍼티: jumps
  } else {
    alert(`상속 프로퍼티: ${prop}`); // 상속 프로퍼티: eats
  }
}

🔔 함수의 prototype 프로퍼티

모든 함수는 기본적으로 prototype 이라는 일반 프로퍼티를 가지며, Function.prototype 은 함수 Function 의 프로토타입을 의미합니다.

Function.prototypenew Function 문법으로 객체를 생성할 때 활용됩니다.
생성자 함수의 프로토타입이 객체라면 new 연산자로 생성되는 객체의 [[prototype]] 은 생성자함수의 Function.prototype 을 참조합니다.

  1. 생성자 함수로 인스턴스 객체 생성
  2. 인스턴스의 [[Prototype]] 이 생성자 함수의 prototype 프로퍼티 참조

함수의 prototype 프로퍼티는 디폴트로 constructor 프로퍼티 하나만 존재하는 객체인
{constructor: Function} 를 가리킵니다.

이 때, constructor 는 함수 자신을 가리킵니다.

function Rabbit() {}
// 디폴트 prototype:
// Rabbit.prototype = { constructor: Rabbit }

let rabbit = new Rabbit(); // {constructor: Rabbit}을 상속받음

alert(rabbit.constructor == Rabbit); // true ([[Prototype]]을 거쳐 접근함)

생성자 함수로 만들어진 객체는 Rabbit.prototype 을 프로토타입으로 지정하여, 자신에게 없는 기능을 프로토타입에서 가져와 사용할 수 있습니다.

function Rabbit(){
    this.name = "RR";
    this.jump = true;
};

let rabbit = new Rabbit();

Rabbit.prototype.ears = "long"; // 프로토타입에 기능 확장

alert(rabbit.ears); // long

Rabbit.prototype 에 프로퍼티를 추가하자 rabbit 객체도 변화된 내용을 활용할 수 있습니다.

그러나, Rabbit.prototype 이 참조하는 대상 자체를 바꿔버리면, 즉 Rabbit.prototype 을 재할당하면 그 순간부터 rabbitRabbit.prototype 은 서로 별개의 존재가 됩니다.
서로 다른 두 대상을 가리키므로, 프로토타입의 변화가 영향을 미치지 않습니다.

  • Function.prototype{ constructor: Function }
  • 인스턴스의 [[Prototype]] 은 생성자의 프로토타입에 대한 참조를 담고있음
  • 인스턴스는 Function.prototype 의 프로퍼티에 [[Prototype]] 을 사용하여 접근
  • 인스턴스의 고유 정보, 상태값은 인스턴스 프로퍼티로, 인스턴스들이 모두 갖는 공통 정보는 프로토타입의 프로퍼티로 지정

Function.prototype 은 객체로, Object 에 의해 생성되죠.
따라서 따로 지정하지 않아도 모든 객체는 프로토타입 체인 최상위에 Object.prototype 을 가집니다.
Object.prototype 의 메서드를 사용하면 하위에서 따로 재정의하지 않는 한 그 메서드를 그대로 사용합니다.


함수의 프로퍼티와 프로토타입의 프로퍼티

Object 를 보면 함수라고 나오는데, 이 함수 자체가 가지는 프로퍼티가 많습니다.
이 중에 prototype 프로퍼티가 가리키는 객체가 생성자 함수 Object 로 만든 객체의 프로토타입이 되는거죠.

그 외에 Object 함수 자체의 프로퍼티는 인스턴스가 프로토타입 체이닝을 사용하여 참고할 수 없습니다.
그냥 Object.assign(...) 이렇게 함수의 메서드 형식으로 불러 인스턴스를 매개변수로 넘겨주는 방식으로 사용합니다.

  • 인스턴스가 호출할 수 있는 건 프로토타입의 메서드
  • 함수가 가지는 프로퍼티는 인스턴스에서 호출 ❌. 함수명.프로퍼티 로 호출

내장 객체의 프로토타입

자바스크립트의 모든 내장 생성자 함수에서 prototype 프로퍼티를 사용할 수 있습니다.
객체 리터럴 문법 {}new Object() 를 간단히 나타낸 것으로, 둘의 동작은 동일합니다.
따라서 일반적으로 생성된 모든 객체는 Object.prototype 을 상속받습니다.

Object.prototype 은 프로토타입 상속 트리 꼭대기에 위치하며, 다른 내장 객체들이 이를 상속받는 구조입니다.

Array.prototype.__proto__ === Object.prototype; // true

원시값과 래퍼 객체

문자열, 숫자, 불린값 등 객체가 아닌 원시 타입에는 프로토타입이 없습니다.
그러나 이런 원시 타입의 프로퍼티에 접근하려 할 때 임시 래퍼 객체가 생성되며, 래퍼 객체의 프로토타입에 구현한 메서드를 사용하도록 규정합니다.

let str = "12345";

str.length; // 5
// 임시 래퍼 객체 String 생성
// String.prototype 에서 length 를 가져와 반환

이렇게 해서 원시 타입의 프로퍼티, 메서드 사용을 보장합니다.
다만 nullundefined 는 래퍼 객체가 없습니다.


모던하게 프로토타입 다루기

__proto__ 는 구식 문법으로, 모던하게 프로토타입을 다루기 위한 메서드는 다음과 같습니다.

  • Object.create(proto, [descriptors]): [[Prototype]]proto 를 참조하는 빈 객체를 생성하여 반환. 이때 프로퍼티 설명자를 추가로 넘길 수 있음
  • Object.getPrototypeOf(obj): obj[[Prototype]]을 반환
  • Object.setPrototypeOf(obj, proto): obj[[Prototype]]proto 가 되도록 설정
profile
기록용 블로그 + 오류가 있을 수 있습니다🔥

0개의 댓글