[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을 참조함