prototype object

자바스크립트는 '클래스'라는 개념이 없다. 그래서 기존의 객체를 복사해 새로운 객체를 생성하는 프로토타입 기반의 언어라고 불린다. 이렇게 생성된 객체는 또 다른 객체의 원형이 될 수 있다.

클래스가 없으니 기본적으로 상속기능도 없다.
그래서 프로토타입을 기반으로 상속을 구현해 사용한다.
(ES6에서 클래스가 추가 됨.)

자바스크립트의 객체 생성 방법

var person = {
  name: 'Kim',
  score: 90
};

// student 에는 hasOwnProperty 메소드가 없지만 아래 구문은 동작한다.

console.log(student.hasOwnProperty('name'); // true

console.dir(person);

스크린샷 2019-11-29 오후 12.35.06.png

스크린샷 2019-11-29 오후 12.28.55.png

person 객체는 __ proto __ property로 자신의 부모 객체(프로토타입 객체)인 Object.prototype을 가리키고 있다.

[[Prototype]]의 값은 null 또는 객체이며 상속을 구현하는데 사용된다.
[[Prototype]] 객체의 데이터 property는 get 액세스를 위해 상속되어 자식 객체의 property처럼 사용할 수 있다. 하지만 set 액세스는 허용되지 않는다.

[[Prototype]] VS prototype property

일반 객체가 [[Prototype]]을 갖고 상속을 위해 사용하는 것과 달리, 함수 객체는 prototype property도 소유하게 된다.

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

var foo = new Person('Lee');

console.dir(Person); // prototype property가 있다.

console.dir(foo);    // prototype property가 없다.

[[Prototype]]

  • 함수를 포함한 모든 객체가 가지고 있다.
  • 객체의 입장에서 부모 역할을 하는 프로토타입 객체를 가리키며 함수 객체의 경우 Function.prototype 을 가리킨다.
console.log(Person.__proto__ === Function.prototype);

prototype property

  • 함수 객체만 갖고 있는 property 이다.
  • 함수 객체가 생성자로 사용될 때, 이 함수를 통해 생성될 객체의 부모 역할을 하는 객체(프로토타입 객체)를 가리킨다.
console.log(Person.prototype === foo.__proto__);

constructor property

프로토타입 객체는 constructor property를 갖는다.

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

var foo = new Person('Lee');

// Person() 생성자 함수에 의해 생성된 객체를 생성한 객체는 Person() 생성자 함수이다.
console.log(Person.prototype.constructor === Person);

// foo 객체를 생성한 객체는 Person() 생성자 함수이다.
console.log(foo.constructor === Person);

// Person() 생성자 함수를 생성한 객체는 Function() 생성자 함수이다.
console.log(Person.constructor === Function);

Prototype chain

자바스크립트는 특정 객체의 프로퍼티나 메소드에 접근하려고 할 때 해당 객체에 접근하려는 프로퍼티 또는 메소드가 없다면 [[Prototype]]이 가리키는 링크를 따라 자신의 부모 역할을 하는 프로토타입 객체의 프로퍼티나 메소드를 차례대로 검색한다. 이 때 Object.prototype 객체를 프로토타입 체인의 종점이라 한다.

var student = {
  name: 'Lee',
  score: 90
}
console.dir(student);
console.log(student.hasOwnProperty('name')); // true
console.log(student.__proto__ === Object.prototype); // true
console.log(Object.prototype.hasOwnProperty('hasOwnProperty')); // true
function Person(name, gender) {
  this.name = name;
  this.gender = gender;
  this.sayHello = function(){
    console.log('Hi! my name is ' + this.name);
  };
}

var foo = new Person('Lee', 'male');

console.dir(Person);
console.dir(foo);

console.log(foo.__proto__ === Person.prototype);                // ① true
console.log(Person.prototype.__proto__ === Object.prototype);   // ② true
console.log(Person.prototype.constructor === Person);           // ③ true
console.log(Person.__proto__ === Function.prototype);           // ④ true
console.log(Function.prototype.__proto__ === Object.prototype); // ⑤ true

프로토타입 객체의 확장

프로토다입 객체도 객체이므로 일반 객체와 같이 property를 추가/삭제할 수 있다. 이렇게 추가/삭제된 property는 프로토타입 체인에 반영된다.

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

var foo = new Person('Lee');

Person.prototype.sayHello = function(){
  console.log('Hi! my name is ' + this.name);
};

foo.sayHello();

원시 타입(Primitive data type)의 확장

자바스크립트에서 원시 타입(숫자, 문자열, boolean, null, undefined)을 제외한 모든 것은 객체이다. 원시타입으로 프로퍼티나 메소드를 호출할 때 원시 타입과 연관된 객체로 일시적으로 변환되어 프로토타입 객체를 공유하게 된다.
원시 타입은 객체가 아니므로 프로퍼티나 메소드를 직접 추가할 수 없다.

var str = 'test';
console.log(typeof str);                 // string
console.log(str.constructor === String); // true
console.dir(str);                        // test

var strObj = new String('test');
console.log(typeof strObj);                 // object
console.log(strObj.constructor === String); // true
console.dir(strObj);
// {0: "t", 1: "e", 2: "s", 3: "t", length: 4, __proto__: String, [[PrimitiveValue]]: "test" }

console.log(str.toUpperCase());    // TEST
console.log(strObj.toUpperCase()); // TEST

프로토타입 객체의 변경

프로토타입 객체를 변경하면,

  • 프로토타입 객체 변경 시점 이전에 생성된 객체는 기존 프로토타입 객체를 [[Prototype]]에 바인딩한다.
  • 프로토타입 객체 변경 시점 이후에 생성된 객체는 변경된 프로토타입 객체를 [[Prototype]]에 바인딩한다.
function Person(name) {
  this.name = name;
}

var foo = new Person('Lee');

// 프로토타입 객체의 변경
Person.prototype = { gender: 'male' };

var bar = new Person('Kim');

console.log(foo.gender); // undefined
console.log(bar.gender); // 'male'

console.log(foo.constructor); // ① Person(name)
console.log(bar.constructor); // ② Object()

프로토타입 체인 동작 조건

  • 객체의 프로퍼티를 참조하는 경우, 해당 객체에 프로퍼티가 없는 경우, 프로토타입 체인이 동작한다.
  • 객체의 프로퍼티에 값을 할당하는 경우, 프로토타입 체인이 동작하지 않는다. 이는 객체에 해당 프로퍼티가 있는 경우, 값을 재할당하고 해당 프로퍼티가 없는 경우는 해당 객체에 프로퍼티를 동적으로 추가하기 때문.
function Person(name) {
  this.name = name;
}

Person.prototype.gender = 'male'; // ①

var foo = new Person('Lee');
var bar = new Person('Kim');

console.log(foo.gender); // ① 'male'
console.log(bar.gender); // ① 'male'

// 1. foo 객체에 gender 프로퍼티가 없으면 프로퍼티 동적 추가
// 2. foo 객체에 gender 프로퍼티가 있으면 해당 프로퍼티에 값 할당
foo.gender = 'female';   // ②

console.log(foo.gender); // ② 'female'
console.log(bar.gender); // ① 'male'

참고자료

https://poiemaweb.com/js-prototype
https://medium.com/@bluesh55/javascript-prototype-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-f8e67c286b67