在 JavaScript 繼承機制 中,prototype 扮演很重要的角色,然而什麼叫做 Prototype Chain 呢?
prototype 與 __proto__
現在更好的用法是
Object.getPrototypeOf()
,雖然有些瀏覽器還是支援__proto__
,但應該盡量避免使用,詳情請見 Object.prototype.__proto__
先來看看這個例子:
function Person(name) {
this.name = name
}
Person.prototype.sayHello = function() {
console.log(this.name + ': hello~')
}
var p1 = new Person('allen')
var p2 = new Person('kaneshiro')
p1.sayHello() // allen: hello~
p2.sayHello() // kaneshiro: hello~
console.log(p1.sayHello === p2.sayHello) // true
我們有一個建構函式 Person
,且它有兩個 instance p1
跟 p2
,還有一個放在 Person.prototype
裡面的方法 sayHello
,這樣可以讓 p1
跟 p2
共用 sayHello
這個方法。
但是我們明明是把 sayHello
放在 Person.prototype
,為什麼 p1
跟 p2
這樣就可以用了呢?
其實當我們 new
了一個新的 instance 時,會發生以下幾件事:
- 建立一個空白的 JavaScript 物件
- 新增
__proto__
屬性到該新物件,這個屬性會指向建構函式的prototype
物件 - 把
this
綁定到該新物件,也就是建構函式中的this
會指向該新物件 - 如果函式沒有 return 任何東西,就會 return
this
所以 p1.__proto__
會指向 Person.prototype
,當 JavaScript 在 p1
找不到 sayHello
這個方法,就會透過 p1.__proto__
找到 Person.prototype
,並在 Person.prototype
找到 sayHello
。
假設在 Person.prototype
還是沒有找到,就會在往 Person.prototype.__proto__
去找,直到某個東西的 __proto__
是 null
為止,代表找到最上層了。這樣一直用點相連起來的鏈結,就叫做原型鏈(Prototype Chain)
寫成程式碼應該會比較有感覺:
function Person(name) {
this.name = name
}
Person.prototype.sayHello = function() {
console.log(this.name + ': hello~')
}
var p1 = new Person('allen')
console.log(p1.__proto__ === Person.prototype) // true
console.log(Person.prototype.__proto__ === Object.prototype) //true
console.log(Object.prototype.__proto__) // null
hasOwnProperty
而要知道一個屬性或方法是存在 instance 自己裡面還是在他的原型鏈上,可以用 hasOwnProperty
這個方法來判斷
function Person(name) {
this.name = name
}
Person.prototype.sayHello = function() {
console.log(this.name + ': hello~')
}
var p1 = new Person('allen')
console.log(p1.hasOwnProperty('sayHello')) // false
console.log(p1.__proto__.hasOwnProperty('sayHello')) // true
如此可以知道 sayHello
是 p1
的原型鏈上的方法,而不是自己的方法。
我們也可以自己寫一段程式碼,來找到這個 sayHello
方法並執行他:
function Person(name) {
this.name = name
}
Person.prototype.sayHello = function() {
console.log(this.name + ': hello~')
}
var p1 = new Person('allen')
function call(obj, methodName) {
var methodOwner = obj
// 不斷往上找,直到找到真正擁有這個 method 的人,或 methodOwner 已經是 null(到最上層了)
while(methodOwner && !methodOwner.hasOwnProperty(methodName)) {
methodOwner = methodOwner.__proto__
}
// 找不到 method 時 methodOwner 會是 null,印出資訊並 return
if (!methodOwner) return console.log('method not found')
// 找到 method,用 apply 的方式來呼叫,this 的值才會正確
methodOwner[methodName].apply(obj)
}
call(p1, 'sayHello') // allen: hello~
call(p1, 'sayByeBye') // method not found
現在可以思考看看,Person.__proto__
會是什麼?
來人上程式碼
function Person(name) {
this.name = name
}
Person.prototype.sayHello = function() {
console.log(this.name + ': hello~')
}
var p1 = new Person('allen')
console.log(Person.__proto__ === Function.prototype) // true
其實 Person
就是 Function
的 instance 啦!所以 Person.__proto__
就是 Function.prototype
!
instanceof
用 A instanceof B
可以判斷 A 不不是 B 的 instance。
function Person(name) {
this.name = name
}
Person.prototype.sayHello = function() {
console.log(this.name + ': hello~')
}
var p1 = new Person('allen')
console.log(p1 instanceof Person) // true
console.log(Person instanceof Object) // true
console.log(Person instanceof Function) // true
console.log(Person instanceof Array) // false
其實原理也很簡單,只要檢查 A 的原型鏈裡有沒有 B 的 prototype 就好了,像是這樣:
function Person(name) {
this.name = name
}
Person.prototype.sayHello = function() {
console.log(this.name + ': hello~')
}
var p1 = new Person('allen')
function instanceOf(A, B) {
// 找到最上層了還是找不到
if(!A) return false
// 找不到的話,就往 A 的上層繼續找
return A.__proto__ === B.prototype ? true : instanceOf(A.__proto__, B)
}
constructor
每個 prototype
還有一個屬性叫做 constructor
,這個屬性會指向建構函式,也就是說 Person.prototype.constructor
就是 Person
。
function Person(name) {
this.name = name
}
Person.prototype.sayHello = function() {
console.log(this.name + ': hello~')
}
var p1 = new Person('allen')
console.log(Person.prototype.constructor === Person) // true
參考資料
該來理解 JavaScript 的原型鍊了
new operator
Object.prototype.__proto__