JavaScript原型和原型鏈新解
為什麼說是新解呢?
看了網上很多描述JS原型的文章,大部分都是這樣描述的,JS物件分為兩種,一個是普通物件,另外一個則是原型物件,其中原型物件包含prototype屬性,普通物件則沒有,還有就是,二者都包含__proto__來實現原型鏈等等
我相信很多人看完後,都是死記硬背的記住了上面的內容,至於為什麼JS要分兩種物件,原因是什麼?估計都不是很清楚,因為大家都是基於結果來分析的
這裡我嘗試換一種角度去分析並理解這塊內容,希望對大家能有所幫助
通常我們在JS中建立物件有兩種方法:
1:function - 建立對應上面說的原型物件
2:new - 建立上面所說的普通物件
我們這裡先想下,這兩種被建立的物件最本質的區別是什麼?我們建立物件的目的是什麼?當然是儲存資料了,
所以,它們最大的不同,是儲存的資料型別不同
1:function建立的物件,是用於儲存程式執行的程式碼描述的
2:new建立的物件,是基於function描述的程式碼來建立的例項
第二點很重要,我再重複一遍,要通過new建立物件,它的前提肯定是類程式碼,那function就是那個儲存了類程式碼描述的物件!!
看程式碼:
function test(){
let var1 = 'harish';
}
var obj1 = new test();
這段程式碼的執行步驟:
1: 檢測到function關鍵字,建立一個叫test物件,然後將function內部執行的程式碼和屬性資料儲存到其中
2:檢測到new關鍵字,從而建立一個新物件儲存到obj1中, 並將obj1的constructor屬性設定為test
3:接著執行obj1.constructor,並將函式執行上下文中的this屬性設定為obj1,從而完成obj1資料的初始化
物件obj1建立完後,大家會發現有點不對勁,這個function描述的類好像只有建構函式,沒有成員變數和成員函式的定義,沒地方定義,那繼承什麼的就是無從談起了,
所以強大的JS就約定,只要是function類物件,就可以通過prototype屬性來描述這個類的成員變數和成員函式。
把程式碼改下:
function test(){ let var1 = 'harish'; } test.prototype.harish = 'dog'; test.prototype.testfunc = function(){ alert('test1 = ' + this.harish); } var obj1 = new test(); obj1.testfunc();
然後上面的程式碼執行步驟改為:
1: 檢測到function關鍵字,建立一個叫test物件,然後將function內部執行的程式碼和屬性資料儲存到其中
2:檢測到new關鍵字,從而建立一個新物件儲存到obj1中, 並將obj1的constructor屬性設定為test
3:將test類的成員變數和成員函式描述物件prototype索引儲存到obj1的__proto__屬性中
4:接著執行obj1.constructor,並將函式執行上下文中的this屬性設定為obj1,從而完成obj1資料的初始化
5:呼叫obj1.testfunc(), JS先看obj1物件的屬性列表中是否存在testfunc的定義,這裡顯然沒有
6:接著從obj1.__proto__中查詢,找到testfunc並執行
接著總結下:
1:原型,即prototype,是function類描述其成員變數和成員函式的,而function函式自身,則在類建立的過程中會被當做建構函式執行
2:__proto__是原型鏈的基礎,在JS中,所有的物件查詢屬性變數,規則總是這樣的,先看物件自身是否存在該屬性,如果不存在,則看__proto__屬性是否為null,如果不是,則查詢__proto__對應物件是否存在該屬性,如此反覆,直到找到或者__proto__為null為止
原型和原型鏈是JS實現繼承的基礎
下面說下JS是如何基於二者實現繼承的:
function A function B function C
prototype prototype prototype
我們先定義三個類,在JS中,即三個function型別的物件A,B,C,然後還為每個類分別定義了成員變數和函式儲存到prototype
類有了,接著我們希望 A繼承自B,B繼承自C
上面說過了JS物件對屬性的查詢規則,基於該規則,就可以很容易的實現繼承
new A() function A function B function C
__proto__ -> prototype.__proto__ -> prototype.__proto__ -> prototype.__proto__(null)
上面箭頭指向的是prototype物件
現在回過頭來,你們應該明白,為什麼new A()建立是普通物件沒有prototype屬性了吧,因為prototype物件描述的是類的成員函式和變數的定義,而這個資料是用來建立普通邏輯物件的,普通邏輯物件不需要也不應該包含prototype變數
最後,拿網上朋友的一個例子作為收尾:
var animal = function(){};
var dog = function(){};
animal.price = 2000;//
dog.prototype = animal;
var tidy = new dog();
console.log(dog. price) //undefined
console.log(tidy.price) // 2000
輸出結果為什麼是這樣?原因是啥?大家自行思考,這裡就不做介紹了^_^