[Javascript log] 프로토타입과 프로토타입 상속

프로토타입 상속

자바스크립트 언어의 고유기능인 프로토타입 상속을 이용하면 상당히 유사하지만 약간의 차이가 있는 객체를 만들때 사용된다. 자바스크립트의 객체는 명세서에서 명명한 [[Prototype]]이라는 숨김 프로퍼티를 갖는다. 이 숨김 프로퍼티 값은 null이거나 다른 객체에 대한 참조가되는데, 다른 객체를 참조하는 경우 참조 대상을 '프로토타입(prototype)'이라 부른다.

object에서 프로퍼티를 읽으려고 하는데 해당 프로퍼티가 없으면 자바스크립트는 자동으로 프로토타입에서 프로퍼티를 찾는다. 프로그래밍에선 이런 동작 방식을 '프로토타입 상속’이라 부른다.
proto는 항상 객체이거나 null이어야 한다. 문자열은 프로토타입이 될 수 없다.

let animal = {
  eats: true
};
let rabbit = {
  jumps: true
};

rabbit.__proto__ = animal; // (*)

alert( rabbit.eats ); // true (**)
alert( rabbit.jumps ); // true

=> rabbit은 animal을 상속받는다.

프로토타입은 프로퍼티를 읽을 때만 사용한다. 프로퍼티를 추가, 수정하거나 지우는 연산은 객체에 직접해야한다.

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

let rabbit = {
  __proto__: animal
};

rabbit.walk = function() {
  alert("토끼가 깡충깡충 뜁니다.");
};

rabbit.walk(); // 토끼가 깡충깡충 뜁니다.

그런데 접근자 프로퍼티(accessor property)는 setter 함수를 사용해 프로퍼티에 값을 할당하므로 접근자 프로퍼티에 값을 할당((**))하면 객체(admin)에 프로퍼티(fullName)가 추가되는게 아니라 setter 함수가 호출되면서 위 예시와는 조금 다르게 동작한다.

let user = {
  name: "John",
  surname: "Smith",

  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); // John Smith (*)

// setter 함수가 실행됩니다!
admin.fullName = "Alice Cooper"; // (**)

alert(admin.fullName); // Alice Cooper, setter에 의해 추가된 admin의 프로퍼티(name, surname)에서 값을 가져옴
alert(user.fullName); // John Smith, 본래 user에 있었던 프로퍼티 값
let animal = {
  eats: true
};

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

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

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

함수의 prototype 프로퍼티

생성자 '함수’를 사용해 객체를 만든 경우에 프로토타입이 어떻게 동작하는지?

생성자 함수로 객체를 만들었을 때 리터럴 방식과 다른점은 생성자 함수의 프로토타입이 객체인 경우에 new 연산자를 사용해 만든 객체는 생성자 함수의 프로토타입 정보를 사용해 [[Prototype]]을 설정한다는 것이다.

let animal = {
  eats: true
};

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

Rabbit.prototype = animal;

let rabbit = new Rabbit("흰 토끼"); //  rabbit.__proto__ == animal

alert( rabbit.eats ); // true

개발자가 특별히 할당하지 않더라도 모든 함수는 기본적으로 "prototype" 프로퍼티를 갖는다. 디폴트 프로퍼티 "prototype"은 constructor 프로퍼티 하나만 있는 객체를 가리키는데, 여기서 constructor 프로퍼티는 함수 자신을 가리킨다.

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

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

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

내장객체의 프로토타입

모든 내장 생성자 함수에서 prototype 프로퍼티를 사용한다.

let obj = {};
alert( obj ); // "[object Object]" ?

obj = new Object()를 줄이면 obj = {}가 된다. 여기서 Object는 내장 객체 생성자 함수인데, 이 생성자 함수의 prototype은 toString을 비롯한 다양한 메서드가 구현되어있는 거대한 객체를 참조한다.

let arr = [1, 2, 3];

// arr은 Array.prototype을 상속받았나요?
alert( arr.__proto__ === Array.prototype ); // true

// arr은 Object.prototype을 상속받았나요?
alert( arr.__proto__.__proto__ === Object.prototype ); // true

// 체인 맨 위엔 null이 있습니다.
alert( arr.__proto__.__proto__.__proto__ ); // null

문자열과 숫자, 불린값은 객체가 아니다. 그런데 이런 원시 타입 값의 프로퍼티에 접근하려고 하면 내장 생성자 String, Number, Boolean을 사용하는 임시 래퍼(wrapper) 객체가 생성된다. 임시 래퍼 객체는 이런 메서드를 제공하고 난 후에 사라진다. 래퍼 객체는 보이지 않는 곳에서 만들어지는데 엔진에 의해 최적화가 이뤄진다. 그런데 명세서엔 각 자료형에 해당하는 래퍼 객체의 메서드를 프로토타입 안에 구현해 놓고 String.prototype, Number.prototype, Boolean.prototype을 사용해 쓰도록 규정정하고있다.

네이티브 프로토타입 변경하기 : 네이티브 프로토타입은 수정가능하다. String.prototype에 메서드를 하나 추가하면 모든 문자열에서 해당 메서드를 사용할 수 있다.

String.prototype.show = function() {
  alert(this);
};

"BOOM!".show(); // BOOM!

프로토타입 메서드와 proto가 없는 객체

proto는 브라우저를 대상으로 개발하고 있다면 다소 구식이기 때문에 더는 사용하지 않는 것이 좋다.

대신 아래와 같은 모던한 메서드들을 사용하는 것이 좋다.

Object.create(proto, [descriptors]) – [[Prototype]]이 proto를 참조하는 빈 객체를 만든다. 이때 프로퍼티 설명자를 추가로 넘길 수 있다.
Object.getPrototypeOf(obj) – obj의 [[Prototype]]을 반환합니다.
Object.setPrototypeOf(obj, proto) – obj의 [[Prototype]]이 proto가 되도록 설정합니다.

let animal = {
  eats : true
};

let rabbit = Object.create(animal);

alert(rabbit.eats);

alert(Object.getPrototypeOf(rabbit)===animal);
Object.setPrototypeOf(rabbit, {}); // rabbit의 프로토타입을 {}로 바꾼다.

좋은 웹페이지 즐겨찾기