avatar
Published on

Javascript Prototype

Author
  • avatar
    Name
    yceffort

Table of Contents

프로토타입

상속이라는 관점에서 봤을 때, 자바스크립트의 유일한 생성자는 객체 뿐이다. 모든 객체는 [[prototype]] 이라는 private 속성을 가지고 있는데, 이는 자신의 프로토타입이 되는 다른 객체를 가리킨다. 이렇게 자신의 프로토타입의 프로토타입의 프로토타입을 따라가다보면, 결국 null을 프로토타입으로 가지는 오브젝트에서 끝난다. null은 프로토타입이 더이상 없다고 정의되며 이는 프로토타입의 종점을 말한다.

var obj = { a: 'hello' }
console.dir(obj)
proto

속성 상속

객체의 어떠한 속성에 접근하려고 할 때, 그 객체 자체의 속성 뿐만 아니라 객체의 프로토타입, 그 프로토 타입의 프로토 타입 등등등 앞서 말한 프로토타입의 종단 까지 갈 때가지 그 속성을 탐색한다.

let foo = function () {
  this.a = 1
  this.b = 2
}

let bar = new foo() // {a:1, b:2}

foo.prototype.b = 3
foo.prototype.c = 4

// bar가 a 속성을 가지고 있기 때문에 1
console.log(bar.a)
// bar가 a 속성을 가지고 있기 때문에 2
console.log(bar.b)
// bar가 c의 속성을 가지고 있지 않다. 그래서 프로토타입을 체크한다.
// foo.[[prototype]] 이 c를 갖고 있는지 확인하자.
// c가 있다.
// 4
console.log(bar.c)
// 프로토타입을 뒤져도 d는 나오지 않는다.
console.log(bar.d)

메소드 상속

var foo = {
  a: 2,
  b: function (c) {
    return this.a + 1
  },
}

// 3
// 여기서 this는 foo를 가리킨다
console.log(foo.b())

// bar는 프로토타입을 foo로 가지는 오브젝트다.
var bar = Object.create(foo)

bar.a = 4
// bar.b()를 호출 하면, this는 bar를 가리킨다.
// 따라서 foo의 함수 b를 상속 받으며,
// a는 foo.a가 아닌 bar에서 새로지정한 a를 보게된다.
// 5
console.log(bar.b())

How to use

function dummy() {}
console.dir(dummy.prototype)
null

여기에 속성을 추가해보자.

function dummy() {}
dummy.prototype.foo = 'bar'
console.dir(dummy.prototype)
null

foo가 bar값으로 추가된 것을 볼 수 있다.

function dummy() {}
dummy.prototype.foo = 'bar'
var d = new dummy()
d.hello = 'world'
console.log(d)
null
  1. d 의 속성에 접근할 때, 브라우저는 우선 d가 그 속성을 가지고 있는지 확인한다.
  2. 만약 d가 해당 속성을 가지고 있지 않다면, d.__proto__ (dummy.prototype)이 그 속성을 가지고 있는지 확인한다.
  3. 만약 dummy.__proto__가 그 속성을 가지고 있다면, dummy.__proto__가 갖고 있는 속성을 사용한다.
  4. 만약 dummy.__proto__마저 그 속성을 가지고 있지 않으면, dummy.__proto__.__proto__가 가지고 있는지 확인한다. 기본적으로 여기서 함수의 prototype의 __proto__window.Object.prototype이다.
  5. 그래서 이제 dummy.__proto__.__proto__ (dummy.prototype__proto__) (Object.prototype)에서 그 속성을 찾는다.
  6. 근데 이제 그 위의 dummy.__proto__.__proto__.__proto__를 찾으려고 하지만, 더이상은 없으므로
  7. undefined로 결론 짓는다.

위의 지독한 과정을 (....) 코드로 살펴보자.

function dummy() {}
dummy.prototype.foo = 'bar'
var d = new dummy()
d.prop = 'some value'
console.log('d.prop:      ' + d.prop)
console.log('d.foo:       ' + d.foo)
console.log('dummy.prop:           ' + dummy.prop)
console.log('dummy.foo:            ' + dummy.foo)
console.log('dummy.prototype.prop: ' + dummy.prototype.prop)
console.log('dummy.prototype.foo:  ' + dummy.prototype.foo)
d.prop:      some value
d.foo:       bar
dummy.prop:           undefined
dummy.foo:            undefined
dummy.prototype.prop: undefined
dummy.prototype.foo:  bar

여러가지 방법으로 객체를 생성하고 프로토타입 체인 결과를 보자

문법 생성자

var foo = { bar: 1 }
// foo의 프로토타입은 Object.prototype

var arrayFoo = ['please', 'go', 'home']
// arrayFoo의 프로토타입은 Array.prototype
// 그래서 map, index 등을 쓸 수 있다.

function functionFoo() {
  return 'fuck'
}
// Function.prototype을 상속받아서
// call, bind 등으르 쓸 수 있다.

생성자를 이용

function hello() {
  this.name = ''
  this.age = 0
}

hello.prototype = {
  setName: function (name) {
    this.name = name
  },
}

var h = new hello()
// h는 name과 age를 속성으로 갖는 객체다.
// 생성이 h.[[prototype]]은 Hello.prototype과 같은 값을 가진다.

Object.create 활용

var a = { a: 1 }
// a --> Object.prototype --> null

var b = Object.create(a)
// b --> a --> Object.prototype --> null

var c = Object.create(b)
// c --> b --> a --> Object.prototype --> null

[[prototype]] vs prototype

모든 객체는 자신의 프로토타입을 가리키는 [[prototype]]이라는 인터널 슬롯을 가지며, 상속을 위해 사용된다. 함수도 객체이기 때문에, [[prototype]]를 갖는다. 그런데 함수는 일반 객체와는 달리 prototype 프로토타입도 갖게 된다. 중요한 것은 [[prototype]]prototype은 다르다는 것이다.

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

var foo = new Person('Lee')

console.dir(Person) // prototype 프로퍼티가 있다.
console.dir(foo) // prototype 프로퍼티가 없다.

function P(name) {
  return name
}

var bar = P('Lee')
console.dir(bar)
null

[[prototype]]

  • 함수를 포함한 모든 객체가 가지고 있는 인터널 슬롯
  • 객체 입장에서 봤을때, 자신의 부모역할을 하는 프로토타입 객체를 가리키며, 함수의 경우 Function.prototype을 가리킨다.

prototype 프로퍼티

  • 오직 함수만 가지고 있는 프로퍼티다.
  • 함수 객체가 생성자로 이용될때, 이 함수를 통해 생성될 객체의 부모역할을 하는 객체를 가리킨다.
  • 위 예제에서 new Person('Lee')는 이함수를 통해 foo가 나왔다. 이 foo__proto__를 가리킨다.

차이비교

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

var foo = new Person('Kim')

console.log(Person.__proto__ === Function.prototype)

console.log(Person.prototype === foo.__proto__)

Constructor

프로토타입 객체는 constructor 프로퍼티를 갖는다. 이 constructor 프로퍼티는 객체입장에서 자신을 생성한 객체를 가리킨다.

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]]은 자바스크립트의 모든 객체가 가진 값이며, __proto__로 접근할 수 있다. (혹은 Object.getPrototypeOf(obj))도 가능하다. 사실 똑같다.) 이것을 이용해 상속을 구현하며, 타고타고 올라가면 Object.prototype을 만나고 Object.__proto__는 null이다.
  • prototypenew로 새로운 object를 만들었을때 (생성자로 사용될때), 이 함수로 생성될 객체의 부모 역할을 할 객체를 가리킨다. (XXX.prototype)
  • XXX.prototype 객체는 constructor를 갖는데, constructor는 자신의 입장에서 자신을 생성한 객체를 가리킨다. 따라서 XXX를 가리킨다.

정리

Foo를 중심으로 설명해보자.

  • Foo는 함수다. 따라서 이 함수의 프로토타입은 Function.prototype이다.
  • Foo를 생성자 함수로 사용했을때, prototype프로퍼티를 갖게되며, Foo.prototype이 생긴다.
  • Foo.prototype의 프로토타입은 Object이다. (함수가 아닌 새로운 객체이므로
  • Foo를 생성자함수로, bcnew를 이용하여 만들었다. bc의 프로토타입은 Foo.prototype이다.
// 생성자 함수
function Foo(y) {
  // Object를 생성한 뒤에, y 프로퍼티에 y값을 갖게 된다.
  this.y = y
}

// 또한 'Foo.prototype'은 Foo의 prototype에 할당되며,
// 이를 활용해서 프로퍼티나 메소드를 상속받거나 공유할 수 있으며,
// 위에서 제시한 예시와 같이 활용할 수 있다.
Foo.prototype.x = 10

// calculate 메소드를 상속받는다
Foo.prototype.calculate = function (z) {
  return this.x + this.y + z
}

// Foo 패턴을 활용하여 b와 c 오브젝트를 생성한다.
var b = new Foo(20)
var c = new Foo(30)

// 상속받은 메소드를 호출한다.
b.calculate(30) // 60
c.calculate(40) // 80

// 한번 확인해보자.
console.log(
  b.__proto__ === Foo.prototype, // true
  c.__proto__ === Foo.prototype, // true

  // Foo.prototype은 constructor라는 새로운 프로퍼티를 만드는데,
  // 이는 함수 그 자체의 생성자를 참조하게 된다.
  // b, c 생성자는 Foo 자체임을 알 수 있다.

  b.constructor === Foo, // true
  c.constructor === Foo, // true
  Foo.prototype.constructor === Foo, // true

  b.calculate === b.__proto__.calculate, // true
  b.__proto__.calculate === Foo.prototype.calculate, // true
)
프로토타입 정리