프로토타입

자바스크립트는 프로토타입 기반의 언어이다. 프로토타입에 기반하여 객체지향 프로그래밍을 지원한다. ES6에서는 class가 추가되었지만 wrapping된 문법이 추가된 것이지 여전히 프로토타입 기반 언어이다.

프로토타입 객체

자바스크립트의 모든 객체는 자신의 부모 역할을 하는 객체와 연결되어있다. 이 부모 객체를 프로토타입 객체 또는 프로토타입이라 한다.

프로토타입 기반 프로그래밍 : 프로토타입을 이용하여 새로운 객체를 만들어내는 프로그래밍 기법

1. prototype 프로퍼티와 [ [ Prototype ] ] 프로퍼티

prototype 프로퍼티

  • 함수 객체만 가지고 있는 프로퍼티이다.
  • 함수 객체가 생성자로 사용될 때, 이 함수를 통해 생성될 객체의 부모 역할을 하는 객체(Prototype 객체)를 가리킨다.
  • 즉, Prototype 객체란 어떠한 객체가 만들어지기 위해 그 객체의 모태가 되는 객체이고 하위 객체들에게 물려줄 속성들이다.

[ [ Prototype ] ] / __proto__ 프로퍼티

  • 함수를 포함한 모든 객체가 가지고 있는 인터널 슬롯이다.
  • 객체의 입장에서 자신의 부모 역할을 하는 Prototype 객체를 가리킨다.
  • ECMAScript에서는 암묵적 프로토타입 링크(implicit prototype link)라 부르며 __proto__ 프로퍼티에 저장된다.

Prototype 프로퍼티는 함수의 입장에서 자신과 링크된 자식에게 물려줄 프로토타입 객체를 가리키고, __proto__ 프로퍼티는 객체의 입장에서 자신의 부모 객체인 프로토타입 객체를 내부의 숨겨진 링크로 가지고 있는 것이다.

constructor 프로퍼티

  • 프로토타입 객체는 constructor 프로퍼티를 가진다.
  • constructor 프로퍼티는 생성된 객체(인스턴스)의 입장에서 자신을 생성한 함수를 가리킨다.

2. 함수가 정의될 때 이루어지는 일

1) constructor(생성자) 자격 부여

  • 해당 함수에 constructor 자격이 부여된다.
  • constructor 자격이 부여되면 new를 통해 객체를 만들 수 있다.
  • 이것이 함수만 new 키워드를 사용할 수 있는 이유이다.

2) 해당 함수의 Prototype 객체 생성 및 연결

  • 함수를 정의하면 함수뿐 아니라 Prototype 객체도 같이 생성된다.
  • Prototype 객체는 일반적인 객체와 같고, 기본적인 속성으로 constructor 프로퍼티와 __proto__ 프로퍼티를 가지고 있다.
function Person() {}

Alt prototype

3. 객체 생성과 프로토타입 체인(Prototype Chain)

프로토타입 체인(Prototype Chain)

  • 자바스크립트는 특정 객체의 프로퍼티나 메소드에 접근시 객체 자신의 것뿐 아니라 __proto__가 가리키는 링크를 따라서 자신의 부모 역할을 하는 프로토타입 객체의 프로퍼티나 메소드를 접근할 수 있다.
  • 즉, 특정 객체의 프로퍼티나 메소드 접근시 만약 현재 객체의 해당 프로퍼티가 존재하지 않는다면 __proto__가 가리키는 링크를 따라 부모 역할을 하는 프로토타입 객체의 프로퍼티나 메소드를 차례로 검색하는 것이 바로 프로토타입 체인이다.
  • 모든 프로토타입 체이닝의 종점은 Object.prototype이다.
  • 하위 객체는 상위 객체의 프로퍼티나 메소드를 상속받는 것이 아니라 공유한다.
  • 해당 객체에 없는 프로퍼티나 메소드를 접근할 때 프로토타입 체이닝이 일어난다.

자바스크립트의 기본 데이터 타입은 Number.prototype, String.prototype 등과 같이 이들의 프로토타입 객체에 정의된 표준 메서드를 사용한다. 그리고 이들 프로토타입 객체 또한 Object.prototype을 자신의 프로토타입(__proto__ 링크)으로 가지고 프로토타입 체이닝으로 연결된다.

3가지의 객체 생성 방법

  1. 리터럴 방식
  2. Object 생성자 함수
  3. 생성자 함수

1) 리터럴 방식으로 생성된 객체의 프로토타입 체인

  • 객체 리터럴 방식으로 생성된 객체는 결국 내장 함수인 Object() 생성자 함수로 생성하는 것을 단순화 한 것이다.
  • 자바스크립트 엔진은 객체 리터럴로 객체를 생성하는 코드를 만나면 내부적으로 Object() 생성자 함수를 사용하여 객체를 생성한다.
  • 즉, Object 생성자 함수로 생성한 것과 같다.

2) Object() 생성자 함수로 생성된 객체의 프로토타입 체인

  • Object() 생성자 함수는 함수이므로,
  • 함수 객체인 Object()는 prototype 프로퍼티가 있다.
    • prototype 프로퍼티는 함수 개체가 생성자로 사용될 때 이 함수를 통해서 생성될 객체의 부모 역할을 하는 객체이다.
    • 즉 프로토타입 객체를 가리킨다.
  • 생성된 객체의 __proto__ 프로퍼티는 자신의 부모역할을 하는 객체 즉, 프로토타입 객체를 가리킨다.
var Person = {
  name: "Song",
  age: 30,
};

console.log(Person.__proto__ === Object.prototype); // true
console.log(Object.prototype.constructor === Object); // true
console.log(Object.__proto__ === Function.prototype); // true
console.log(Function.prototype.__proto__ === Object.prototype); // true

Alt prototype

3) 생성자 함수로 생성된 객체의 프로토타입 체인

  • 함수 생성 방법

    • 함수 선언식(Function declaration)
    • 함수 표현식(Function expression)
    • Function() 생성자 함수
  • 생성자 함수로 객체를 생성하기 위해서는 먼저 생성자 함수를 정의해야한다.
  • 함수 선언식의 경우 내부적으로 자바스크립트 엔진이 기명 함수 표현식으로 변환한다.
  • 결국 함수 선언식, 함수 표현식 모두 함수 리터럴을 사용하고, 함수 리터럴은 Function() 생성자 함수로 함수를 생성하는 것을 단순화 시킨 것이다.

어떠한 방식으로 함수 객체를 생성해도 모든 함수 객체의 prototype은 Function.prototype이다. 생성자 함수도 함수 객체이므로 생성자 함수의 프로토타입 객체 또한 Function.prototype이다.

객체 생성 방법에 따른 prototype 객체 정리

객체 생성 방식 엔진의 객체 생성 인스턴스의 prototype 객체
객체 리터럴 Object() 생성자 함수 Object.prototype
Object() 생성자 Object() 생성자 함수 Object.prototype
생성자 함수 생성자 함수 [생성자 함수].prototype
function Person(name, age) {
  this.name = name;
  this.age = age;
}
var song = new Person("song", 30);
console.log(song.__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

Alt prototype

song 객체의 프로토타입인 Person.prototype과 Person() 생성자 함수의 프로토타입인 function.prototype의 프로토타입은 Object.prototype이다. 객체 리터럴 이나 생성자 함수 방식 모두 결국 모든 객체의 부모 객체인 Object.prototype에서 프로토타입 체인이 끝난다.

객체 생성 시 tips

  • new Object()는 사용하기보다는 리터럴 방식이 좋다.
  • 리터럴 표기법을 사용하면 유효범위 판별 작업이 일어나지 않는다.
  • new를 강제하는 패턴에서 사용하는 argument.callee는 strict 모드에서는 허용되지 않는다.
  • 함수를 이용한 싱글톤 객체(생성자 함수 + new function)

    • 한 번만 사용하거나 이름 짓기 어려운 생성자 함수가 꼭 필요할 때 사용할 수 있는 방법이다.
    var Song = new (function () {
      this.age = 30;
      this.name = "Song";
    })();
    
    Song.name = "JW";
    

4. 프로토타입 객체의 확장

  • 프로토타입도 객체이므로 일반 객체와 같이 프로퍼티를 추가하거나 삭제할 수 있다.
  • 추가/삭제된 프로퍼티는 즉시 프로토타입 체인에 반영된다.
function Person(name) {
  this.name = name;
}

var song = new Person("Song");

Person.prototype.GetName = function () {
  console.log(this.name);
};

song.GetName();

생성자 함수 Person에 의해 생성된 모든 객체에서 프로토타입 체인에 의해 부모 객체인 Person.prototype의 메소드를 사용할 수 있다. 원시 타입, 빌트인 객체 또한 프로토타입 확장이 가능하다.

var str = "test";

String.prototype.myMethod = function () {
  return "myMethod";
};

console.log(str.myMethod()); // myMethod
console.log("string".myMethod()); // myMethod

5. 프로토타입 객체의 변경

  • 객체를 생성할 때 프로토타입이 결정된다.
  • 결정된 프로토타입 객체는 다른 임의의 객체로 변경할 수 있다.
  • 프로토타입 객체 변경 이전에 생성된 객체
    • 기존 프로토타입 객체가 __proto__에 바인딩 된다.
  • 프로토타입 객체 변경 이후에 생성된 객체
    • 변경된 프로토타입 객체가 __proto__에 바인딩 된다.
function Person(name) {
  this.name = name;
}

var song = new Person("Song");

Person.prototype = { age: 30 };

var kim = new Person("Kim");

console.log(song.age); // undefined
console.log(kim.age); // '30'

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

참고

  • Learning Javascript
  • Inside Javascript
  • https://poiemaweb.com/js-prototype
  • http://insanehong.kr/post/javascript-prototype/
  • https://medium.com/@bluesh55/javascript-prototype-이해하기-f8e67c286b67