상속이라는 관점에서 봤을 때, 자바스크립트의 유일한 생성자는 객체 뿐이다. 모든 객체는 [[prototype]]
이라는 private 속성을 가지고 있는데, 이는 자신의 프로토타입이 되는 다른 객체를 가리킨다. 이렇게 자신의 프로토타입의 프로토타입의 프로토타입을 따라가다보면, 결국 null을 프로토타입으로 가지는 오브젝트에서 끝난다. null은 프로토타입이 더이상 없다고 정의되며 이는 프로토타입의 종점을 말한다.
var obj = { a: 'hello' }
console.dir(obj)
객체의 어떠한 속성에 접근하려고 할 때, 그 객체 자체의 속성 뿐만 아니라 객체의 프로토타입, 그 프로토 타입의 프로토 타입 등등등 앞서 말한 프로토타입의 종단 까지 갈 때가지 그 속성을 탐색한다.
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())
function dummy() {}
console.dir(dummy.prototype)
여기에 속성을 추가해보자.
function dummy() {}
dummy.prototype.foo = 'bar'
console.dir(dummy.prototype)
foo가 bar
값으로 추가된 것을 볼 수 있다.
function dummy() {}
dummy.prototype.foo = 'bar'
var d = new dummy()
d.hello = 'world'
console.log(d)
d.__proto__
(dummy.prototype
)이 그 속성을 가지고 있는지 확인한다.dummy.__proto__
가 그 속성을 가지고 있다면, dummy.__proto__
가 갖고 있는 속성을 사용한다.dummy.__proto__
마저 그 속성을 가지고 있지 않으면, dummy.__proto__.__proto__
가 가지고 있는지 확인한다. 기본적으로 여기서 함수의 prototype의 __proto__
는 window.Object.prototype
이다.dummy.__proto__.__proto__
(dummy.prototype
의 __proto__
) (Object.prototype
)에서 그 속성을 찾는다.dummy.__proto__.__proto__.__proto__
를 찾으려고 하지만, 더이상은 없으므로위의 지독한 과정을 (....) 코드로 살펴보자.
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과 같은 값을 가진다.
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]]
이라는 인터널 슬롯을 가지며, 상속을 위해 사용된다. 함수도 객체이기 때문에, [[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)
Function.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 프로퍼티는 객체입장에서 자신을 생성한 객체를 가리킨다.
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이다.prototype
은 new
로 새로운 object를 만들었을때 (생성자로 사용될때), 이 함수로 생성될 객체의 부모 역할을 할 객체를 가리킨다. (XXX.prototype
)XXX.prototype
객체는 constructor
를 갖는데, constructor
는 자신의 입장에서 자신을 생성한 객체를 가리킨다. 따라서 XXX
를 가리킨다.Foo를 중심으로 설명해보자.
Function.prototype
이다.Foo
를 생성자 함수로 사용했을때, prototype
프로퍼티를 갖게되며, Foo.prototype
이 생긴다.Foo.prototype
의 프로토타입은 Object
이다. (함수가 아닌 새로운 객체이므로Foo
를 생성자함수로, b
와 c
를 new
를 이용하여 만들었다. b
와 c
의 프로토타입은 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
)