在瞭解原型和原型鏈之前首先得明確它倆是什麼東西:
原型:prototype
又稱顯示原型
1、原型是一個普通物件
2、只有建構函式才具備該屬性
3、公有屬性可操作
隱式原型:__proto__
1、只有物件(普通物件、函數物件)具備
2、私有的物件屬性,不可操作
有了上面的概念之後,我們再來探討一下什麼是原型和原型鏈。
prototype
我們定義一個字串變數的時候,該字串本身是不具備任何方法的,但是可以呼叫字串方法。
let str = 'hello' // new String()
console.log(str);
console.log(str.length);
其實我們在定義一個字串變數的時候,隱式的範例化了new String()
這個建構函式,所以我們才可以使用字串方法。
console.log(new String());
這個length
就是String
的原型方法ptototype
,字串本身有沒有這個方法不重要,字串的原型上有個方法就可以了。
原型的本質是一個普通物件
,所以我們可以利用物件.屬性
的方式呼叫方法。
如果我們用字串呼叫一個DCodes()
,該方法在字串屬性上沒有並不存在,呼叫該方法會報錯。
我們給String
的原型新增一個DCodes
方法,字串就可以呼叫該方法了。
String.prototype.DCodes = function(){
console.log('你好DCodes');
}
str.DCodes() // 你好DCodes
利用原型可以幹什麼呢?上面也說了,建構函式才具備原型,我們建立一個建構函式,可以通過範例化這個建構函式來呼叫原型方法和原型屬性。
// 建構函式
function Person(){
this.name = '東方不敗'
}
let per = new Person()
console.log(per);
原型的本質是一個物件,那麼給Person
這個原型新增一個方法
function Person(){
this.name = '東方不敗'
}
Person.prototype.sum = function(a,b){return a + b}
let per = new Person()
console.log(per);
console.log(per.sum(1,2));
建構函式記錄了當前原型物件產生的歸屬,原型是基於那個建構函式構建的,那麼constructor
指向的就是那個建構函式,這裡的constructor
指向的就是Person()
函數。
隱式原型只有物件(普通物件、函數物件)才具備,並且隱式原型是一個私有的物件屬性,不可操作。
上面也提到過,我們定義了一個字串,實際上是隱式的new String()
,String()
的原型上有length
,所以字串可以呼叫length
方法,顯示原型prototype
是建構函式才具備的,普通物件是沒有的,那麼普通物件是怎麼呼叫建構函式的原型方法的呢?答案就是普通物件具有隱式原型,隱式原型全等於顯示原型
:
let hello = 'hello'
console.log(hello.__proto__ === String.prototype); // true
也就是說,普通物件的隱式原型__proto__
等於建構函式的顯示原型prototype
,普通物件就可以呼叫建構函式的原型方法。
谷歌瀏覽器中,隱式原型__proto__
的寫法為: [[Prototype]]
到這裡就構成了原型鏈,用字串呼叫字串方法的時候,字串會在__proto__
尋找對應的字串方法,__proto__
等於prototype
,也就是String()
建構函式,如果String()
的建構函式沒有該方法,那麼String()
會繼續向上尋找,原型prototype
是一個物件,那麼物件就會有隱式原型__proto__
,String()
的隱式原型__proto__
是Object()
,然後會在Object()
的原型prototype
上尋找,如果Object()
的原型prototype
上不存在該屬性,那麼就會通過隱式原型__proto__
繼續向上尋找,直到找到對應的方法為止,如果沒有找到,那麼就會報錯,該方法不存在。(這一段需要好好理解)
這樣向上尋找,最終總會有盡頭,萬物的原型終點是誰呢?
字串、陣列、建構函式的原型最終都會指向Object
,而Object
的原型指向的是null
。
console.log(Object.prototype);
最後我們來看一下prototype、__proto__
之間的關係:
__proto__ === prototype
prototype == {}
{}.__proto__ == Object.prototype
......