理解原型物件(有些文章簡稱為原型)和原型鏈,是理解JS的重要一環。下面是筆者對JS中原型的理解,
函式物件
俗話說,JS中萬物皆物件。函式也是一個物件,只不過函式是在特定環境中執行程式碼的物件。
什麼是函式物件?每宣告一個函式,此函式在JS執行解釋時都會被當作一個物件來維護,這就是函式物件。JS中宣告函式的方式有:
function fn1(){}
var fn2 = function(){}
var fn3 = new Function()
複製程式碼
所以可以理解為fn1、fn2、fn3都是函式物件。JS中還包括一些系統內建的函式物件,比如:
Function Object Array String Number RegExp
複製程式碼
函式物件之外的物件都是普通物件。函式物件能建立普通的物件,反之則不行。
理解原型物件(其實就一普通物件)
1、只有函式物件才擁有原型物件
也即無論什麼時候以什麼方式建立一個函式(函式物件),都會根據特定的規則為該函式建立一個prototype屬性(原型物件的地址的引用),這個屬性就是指向該函式的原型物件。比如:
function Person () {};
console.log(Person.prototype) // Person.prototype就是Person的原型物件,實際是原型物件的記憶體地址的引用
複製程式碼
看到沒有,原型物件並不神祕,就是一個普通的物件,只不過其預設有了constructor
和__proto__
(下一節會講)屬性而已(其中__proto__
不建議在實際中應用,因為在有些瀏覽器可能並沒有實現該屬性)。
由上圖看出,函式Person
的原型物件(Person.prototype
)預設擁有一個屬性constructor
,此屬性就是用來重新指向函式Person
。
function Person () {};
Person.prototype.constructor === Person // true
複製程式碼
2、普通物件與原型物件的關係
一般我們定義一個建構函式(建構函式其實就是普通的函式,只不過目的是建立物件),然後通過new
操作符來建立一個普通物件。
function Person (name) {
this.name = name;
this.age = 18;
}
var xiaoming = new Person('小明'); // {name: '小明', age: 18}
var xiaohong = new Person('小紅'); // {name: '小紅', age: 18}
複製程式碼
在上述程式碼中,變數xiaoming
和xiaohong
是建構函式Person
的例項。我們通過上一節知道了Person
與其原型物件的關係,但例項與建構函式的原型物件有什麼關係呢?
每當呼叫建構函式建立一個例項即普通物件後,該例項將包含一個內部的指標[[Prototype]]
,這個指標指向的就是建構函式的原型物件。
目前ECMAScript的標準中並沒有實現標準的訪問該指標的方式,但像Firefox、Chrome和Safari等瀏覽器實現了__proto__
屬性,此屬性就是用來訪問指標[[Prototype]]
,所以可以借用__proto__
屬性展示例項和原型物件的關係。
xiaoming.__proto__ === Person.prototype // true
xiaohong.__proto__ === Person.prototype // true
複製程式碼
3、總結上述兩小節
每建立一個函式,就會為相應的函式建立一個prototype
的屬性,這個屬性指向了函式的原型物件,這個函式的原型物件會預設擁有一個constructor
屬性,此屬性指向了對應的函式。而使用new
操作符呼叫函式建立出來的例項,會擁有一個內部的指標[[Prototype]]
,此指標指向函式的原型物件。
千言萬語不如一幅圖:
原型鏈
由上節我們可以知道,原型物件上的屬性和方法被所有例項所共享的。每當訪問一個物件的屬性或者方法時,會首先搜尋物件自身,如果找到了此屬性或者方法,則直接返回,否則向對應的原型物件上面搜尋,如果找到則直接返回,否則繼續向原型物件的原型物件上查詢,直到搜尋到null,丟擲錯誤或返回undefined
。
function Person (name) {
this.name = name;
this.age = 18;
}
Person.prototype.sayName () { // 在Person的原型物件上新增的方法,被所有例項共享
console.log(this.name);
}
var xiaoming = new Person('小明'); // {name: '小明', age: 18}
xiaoming.sayName(); // 小明
複製程式碼
上面程式碼中,例項xiaoming
本身並沒有sayName
方法,但卻成功呼叫了。
其實就是通過例項內部的[[Prototype]]
指標去原型物件Person.prototype
上找對應的方法,然後呼叫。
如果我呼叫一個例項本身和原型物件都沒有的方法,其過程是怎麼樣的呢?
xiaoming.sayAge() // 例項本身和原型物件都不存在的方法
複製程式碼
(1)首先搜尋xiaoming
這個物件,並沒有sayAge
方法,
[[Prototype]]
指標)。沒有找到sayAge
方法
(3)繼續向原型物件的原型物件上搜尋,即xiaoming.__proto__.__proto__
。也沒有找到sayAge
方法。
(4)繼續向原型物件的原型物件的原型物件上搜尋,即xiaoming.__proto__.__proto__.__proto__
,但發現xiaoming.__proto__.__proto__.__proto__
為null
,停止搜尋,丟擲錯誤或返回undefined
。
如果原型物件和例項上具有同名的屬性或方法,則搜尋時取最近的。
如上述的原型鏈的搜尋機制,你通過閱讀本文知道xiaoming.__proto__
是Person.prototype
,但xiaoming.__proto__.__proto__
呢?
不說話看圖:
思考
原型鏈中的關係圖其實還缺少一環,就是內建函式Function
。Function
比較特殊,有興趣的可以去研究下Function
與Object
的關係。
本文是筆者對原型物件和原型鏈的理解,如有錯誤或不足的地方,歡迎指正。