1. 程式人生 > >JavaScript原型和原型鏈新解

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
輸出結果為什麼是這樣?原因是啥?大家自行思考,這裡就不做介紹了^_^