理解原型對象(有些文章簡稱爲原型)和原型鏈,是理解JS的重要一環。下面是筆者對JS中原型的理解,瀏覽器
俗話說,JS中萬物皆對象。函數也是一個對象,只不過函數是在特定環境中執行代碼的對象。bash
什麼是函數對象?每聲明一個函數,此函數在JS執行解釋時都會被看成一個對象來維護,這就是函數對象。JS中聲明函數的方式有:函數
function fn1(){}
var fn2 = function(){}
var fn3 = new Function()
複製代碼
因此能夠理解爲fn一、fn二、fn3都是函數對象。JS中還包括一些系統內置的函數對象,好比:this
Function Object Array String Number RegExp
複製代碼
函數對象以外的對象都是普通對象。函數對象能建立普通的對象,反之則不行。spa
也即不管何時以什麼方式建立一個函數(函數對象),都會根據特定的規則爲該函數建立一個prototype屬性(原型對象的地址的引用),這個屬性就是指向該函數的原型對象。好比: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
複製代碼
通常咱們定義一個構造函數(構造函數其實就是普通的函數,只不過目的是建立對象),而後經過new
操做符來建立一個普通對象。code
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
與其原型對象的關係,但實例與構造函數的原型對象有什麼關係呢?cdn
每當調用構造函數建立一個實例即普通對象後,該實例將包含一個內部的指針[[Prototype]]
,這個指針指向的就是構造函數的原型對象。對象
目前ECMAScript的標準中並無實現標準的訪問該指針的方式,但像Firefox、Chrome和Safari等瀏覽器實現了__proto__
屬性,此屬性就是用來訪問指針[[Prototype]]
,因此能夠借用__proto__
屬性展現實例和原型對象的關係。
xiaoming.__proto__ === Person.prototype // true
xiaohong.__proto__ === Person.prototype // true
複製代碼
每建立一個函數,就會爲相應的函數建立一個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
方法
xiaoming.__proto__.__proto__
。也沒有找到
sayAge
方法。
xiaoming.__proto__.__proto__.__proto__
,但發現
xiaoming.__proto__.__proto__.__proto__
爲
null
,中止搜索,拋出錯誤或返回
undefined
。
若是原型對象和實例上具備同名的屬性或方法,則搜索時取最近的。
如上述的原型鏈的搜索機制,你經過閱讀本文知道xiaoming.__proto__
是Person.prototype
,但xiaoming.__proto__.__proto__
呢? 不說話看圖:
原型鏈中的關係圖其實還缺乏一環,就是內置函數Function
。Function
比較特殊,有興趣的能夠去研究下Function
與Object
的關係。
本文是筆者對原型對象和原型鏈的理解,若有錯誤或不足的地方,歡迎指正。