본문 바로가기

Language/JavaScript

<자바스크립트> 프로토타입

 

 

1. 프로토타입 상속

 - 개발에서 기존의 객체가 가지고 있는 기능을 확장해서 비슷한 객체를 만들어야 하는 일이 흔하다. 이 때 사용할 수 있는 것이 프로토타입 상속이다.

 

* [[Prototype]]

 - 자바스크립트 객체는 [[Prototype]] 이라는 숨김 프로퍼티를 갖는다. 이는 null 또는 다른 객체에 대한 참조가 되며, 다른 객체를 참조할 경우 그 대상을 프로토타입이라 부른다.

 

 - 객체에서 프로퍼티를 읽으려고 할 때, 해당 프로퍼티가 없으면 자동으로 프로토타입에서 그 프로퍼티를 찾는데, 이를 '프로토타입 상속'이라 부른다.

 

 - __proto__를 통해 값을 설정할 수 있다. __proto__는 [[Prototype]] 그 자체는 아니며, [[Prototype]]의 getter이자 setter이다. 최근에는 Object.getProtoypeOf와 Object.setPrototypeOf를 써서 프로토타입을 get하거나 set한다.

 

let animal = {
  eats: true,
  walk() {
    alert("동물이 걷습니다.");
  }
};

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

let blackRabbit = {
  color: "black",
  __proto__: rabbit
};

blackRabbit.walk(); // 동물이 걷습니다.
alert(blackRabbit.jumps); // true 

 

 - 위에서 rabbit은 프로토 타입으로 animal을 갖고 blackRabbit은 rabbit을 프로토타입으로 갖는 체인 관계이다.

 

 - blackRabiit에서 walk를 호출하면 먼저 rabbit에서 찾으며, 없으므로 rabbit의 프로토타입에서 다시 찾는다.

 

 - blackRabbit에서 jumps를 호출하였고, 프로토 타입인 rabbit에서 바로 찾아서 호출한다.

 

 - 프로토타입 체이닝을 할 때 주의할 점이 있다. 순환참조는 허용되지 않으며, __proto__의 값은 객체나 null만 가능하다.

 

 - for..in 반복문은 상속 프로퍼티도 순회대상에 포함시키는데, obj.hasOwnProperty(key)를 이용하면 상속 프로퍼티를 순회 대상에서 제외 시킬 수 있다.

 

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
  }
}

 

* this

 - this는 프로토타입에서 어떻게 동작할까? this는 프로토타입과 관계 없이 .앞에 있는 객체만을 바라본다.

 

let animal = {
  sleep() {
    this.isSleeping = true;
  }
};

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

rabbit.sleep();

alert(rabbit.isSleeping); // true
alert(animal.isSleeping); // undefined 

 

 - rabbit.sleep()으로 메서드를 시행하였다. 이 때 rabbit에서 메서드를 찾지 못하고 프로토타입인 animal에서 sleep 메서드를 찾는다. this.isSeeping을 true로 바꾸는데 여기서 this는 처음 sleep을 호출한 rabbit을 가리킨다.

 

2. 함수의 prototype 프로퍼티

* 함수의 prototype

 - new F()와 같은 생성자 함수로 새로운 객체를 만들 수 있다. 이 때 F.prototype이 객체라면 new연산자는 F.prototype을 사용해 새롭게 생성된 객체의 [[Prototype]]을 설정한다.

 

let animal = {
  eats: true
};

function Cat(name) {
  this.name = name;
}

Cat.prototype = animal;

let cat = new Cat("Black Cat"); //  cat.__proto__ == animal

alert( cat.eats ); // true

 

 - 위에서 Cat은 생성자 함수로써 객체를 만들 수 있게 한다.

 

 - Cat.prototype = animal은 new Cat 을 호출해 만든 새 객체의 [[Prototype]]을 animal로 설정하라는 의미를 갖는다.

 

 - 이 때, F.prototype은 new F를 호출할 때만 사용된다. 새 객체가 만들어진 후에 F.prototype 프로퍼티가 바뀌면 기존에 만들어진 객체의 [[Prototype]]은 유지되지만 나중에 new F로 만들어지는 객체는 바뀐 프로퍼티를 [[Prototype]]으로 갖게 된다.

 

* 함수의 constructor

 - 모든 함수는 기본적으로 prototype 프로퍼티를 갖고 있다. 이 때 constructor 프로퍼티 하나만 있는 객체를 가리키며, 이 constructor프로퍼티는 함수 자신을 가리킨다.

 

function Cat() {}

// Rabbit.prototype = { constructor: Cat };

 

 - 이 때 new Cat으로 구현한 객체는 당연히 .constructor을 호출하면 프로토타입을 거쳐 Cat에 도달한다.

 

 - 그러나 위의 Cat에서 임의적으로 prototype을 변경하면 constructor가 사라진다.

 

function Cat() {}
Cat.prototype = {
  jumps: true
};

let cat = new Cat();
alert(cat.constructor === Cat); // false

 

- 위를 보면 prototype이 기존 prototype을 덮으면서 constructor가 사라진것을 알 수 있다. 이를 해결하려면 전체를 덮어 쓰지 않고 따로 Cat.prototype.jumps = true와 같이 추가해주거나 prototype내에 constructor: Cat을 명시하여 수동으로 다시 만들어줄 수 있다.

 


 

 

참고

 

 

모던 자바스크립트 튜토리얼

 

모던 JavaScript 튜토리얼

 

ko.javascript.info