[JavaScript] 프로토타입 (Prototype)


1. 프로토타입 (Prototype)

  • 자바스크립트는 프로토타입 기반 언어
  • 클래스 기반 언어에선 상속을 사용하지만 프로토타입 기반 언어에선느 어떤 객체를 원형(prototype)으로 삼고 이를 복제(참조)함으로서 상속과 비슷한 효과를 나타낸다

1) 프로토타입 개념 이해

(1) 프로토타입 도식화

  • 어떤 생성자 함수 (Constructor)를 new 연산자와 함께 호출하면 새로운 인스턴스가 생성
  • 이때 instance는 __**proto**__라는 프로퍼티가 자동으로 부여됌
  • 이 프로퍼티는 Constructor의 prototype이라는 프로퍼티를 참조함

  • 메서드 호출을 통한 undefined는 에러가 발생하지 않았다는 의미 ⇒ 호출 할 수 있는 함수에 해당
  • Person.prototype과 dongnyeong.proto은 같은 객체를 바라보므로 true
  1. __proto__ 객체에 name 프로퍼티가 존재할 경우
let Person = function (name) {
  this._name = name;
};

Person.prototype.getName = function () {
  return this._name;
};

var dongnyeong = new Person("Dongnyeong");
dongnyeong.__proto__.name = "DONGNYEONG__proto__";
console.log(dongnyeong.__proto__.name); // DONGNYEONG__proto__
  1. __proto__는 생략 가능한 프로퍼티다
var dongnyeong = new Person("Dongnyeong", 28);
console.log(dongnyeong.getName()); // Dongnyeong
var dongnyeong2 = new Person("Dongnyeong2", 29);
console.log(dongnyeong2.getName()); // Dongnyeong2

let Person = function (name) {
  this._name = name;
};

Person.prototype.getName = function () {
  return this._name;
};

var dongnyeong = new Person("Dongnyeong");
console.log(dongnyeong.__proto__.getName()); // undefined

console.log(Person.prototype === dongnyeong.__proto__); // true

간단 요약:

  • 자바스크립트는 함수에 자동으로 객체인 prototype 프로퍼티를 생성함
  • 해당 함수를 생성자 함수로서 사용할 경우 , new 연산자와 함께 함수를 호출할 경우 이로 부터 생성된 인스턴스에는 숨겨진 프로퍼티를 참조함
  • __proto__ 프로퍼티는 생략 가능하도록 구현되있음
  • 생성자 함수의 prototype에 어떤 메서드 또는 프로퍼티가 있다면 인스턴스에서도 마치 자신의 것처럼 해당 메서드나 프로퍼티에 접근 가능하다

(2) constructor 프로퍼티

  • 생성자 함수의 프로퍼티인 prototype 객체 내부에넌 constructor라는 프로퍼티가 있다
  • 생성자 함수(자기 자신)을 참조함
var arr = [1, 2];

console.log(Array.prototype.constructor === Array); // true
console.log(arr.__proto__.constructor === Array); // true
console.log(arr.constructor === Array); // true

var arr2 = new arr.constructor(3, 4);
console.log(arr2); // [3,4]
  1. construtor 변경하기
var NewConstructor = function () {
  console.log("this is new constructor");
};

var dataTypes = [
  1, // Number
  "test", // String
  true, // Boolean
  {}, // Object
  [], // Array
  function () {}, // Function
  /test/, // NewConstructor & false
  new Number(), // NewConstructor & false
  new String(), // NewConstructor & false
  new Boolean(), // NewConstructor & false
  new Object(), // NewConstructor & false
  new Array(), // NewConstructor & false
  new Function(), // NewConstructor & false
  new RegExp(), // NewConstructor & false
  new Date(), // NewConstructor & false
  new Error(), // NewConstructor & false
];

dataTypes.forEach(function (d) {
  d.constructor = NewConstructor;
  console.log(d.constructor.name, d instanceof NewConstructor);
});
  • 모든 데이터가 d instanceof NewConstructor 명령에 false를 반환
  • constructor를 변경하더라도 참조하는 대상이 변경되고 인스턴스의 원형이 바뀌진 않는다
var Person = function (name) {
  this._name = name;
};

var p1 = new Person("사람1");
var p1proto = Object.getPrototypeOf(p1);
var p2 = new Person.prototype.constructor("사람2");
var p3 = new p1proto.constructor("사람3");
var p4 = new p1.__proto__.constructor("사람4");
var p5 = new p1.constructor("사람5");

[p1, p2, p3, p4, p5].forEach(function (p) {
  console.log(p, p instanceof Person);
});

// Person { _name: '사람1' } true
// Person { _name: '사람2' } true
// Person { _name: '사람3' } true
// Person { _name: '사람4' } true
// Person { _name: '사람5' } true

2) 프로토타입 체인

(1) 메서드 오버라이드

var Person = function (name) {
  this._name = name;
};

Person.prototype.getName = function () {
  return this._name;
};

var 아이유 = new Person("지금");
아이유.getName = function () {
  return "바로 " + this._name;
};

console.log(아이유.getName()); // 바로 지금

console.log(아이유.__proto__.getName()); // undefined

Person.prototype.name = "이지금";
console.log(아이유.__proto__.name); // 이지금

console.log(아이유.__proto__.getName.call(아이유)); // 지금
  • 아이유.__proto__.getName이 아닌 아이유 객체에 있는 getName 메서드가 호출 됨
  • 메서드 위에 메서드를 덮어 씌워서 메서드 오버라이드
  • 아이유.__proto__.getName() : undefined ⇒ this가 prototype 객체를 가리키는데 prototype 상 name 프로퍼티가 없어서
  • this가 prototype을 바라보고있는데 이걸 인스턴스로 바꿈 (call/apply)

(2) 프로토타입 체인

var arr = [1, 2];
arr.push(3);
console.log(arr.hasOwnProperty(2)); // true

// arr.push를 실행했을때 __proto__가 실행
// arr.hasOwnProperty(2);를 실행했을떈 __proto__가 2번 실행
  • __proto__ 프로퍼티 내부에서 다시 proto 프로퍼티가 연쇄적으로 이어진것을 프로토타입 체인
  • 체인을 따라가면서 검색하는 것을 프로토타입 체이닝
var arr = [1, 2];
Array.prototype.toString.call(arr); // 1,2
Object.prototype.toString.call(arr); // [object Array]
arr.toString(); // 1,2

arr.toString = function () {
  return this.join("_");
};

arr.toString(); // 1_2
  • arr 변수는 배열이므로 arr._proto는 Array.prototype을 참조하고, Array.prototype은 객체이므로 Array.prototype.proto__는 Object.prototype을 참조함