1. 程式人生 > >我對js原型鏈的理解

我對js原型鏈的理解

引言

js原型鏈是js面向物件程式設計的基礎和重點,許多文章都對它進行了講解,這裡我想談談我對原型鏈的理解,一方面加深自己的印象,另一方面希望能和大家分享交流。

從關鍵字 new 說起

我來試著模擬一下new的操作過程。

//模擬new關鍵字的行為
function methodNew( func ) //func:新物件的建構函式
{
    if ( func === Object )
        return Object(); //Object()方法返回原始的object物件
    else
    {
        var obj = {}; //{}實際上等價於Object()
obj.constructor = func; //更改obj的constructor屬性 obj.__proto__ = func.prototype; //設定obj.__proto__屬性,從而訪問到原型鏈上的屬性, //比如 func.prototype.name可以通過 obj.name讀取,但是不能使用obj.name來修改,這樣會為obj新增一個name屬性。 //這裡只處理func方法引數為空的情況 func.call( obj ); //為obj新增func方法中定義的屬性,比如 //function func(){ this.name = "Jerry" }
//相當於執行obj.name = "Jerry" return obj; } }

來測試一下

function People()
{
    this.name = "楊冪";

    this.introduce = function()
    {
        alert("My name is "+ this.name );
    }
}
    var obj = methodNew( Object );
    var beauty = methodNew( People );   
    console.log( obj ); 
    console.log( beauty );

結果如下

Object {}
People {constructor: function, name: "楊冪", introduce: function} 

結果與new關鍵字生成的物件一致。
稍微總結一下,new關鍵字的行為與後面的方法是不是 Object() 有關,
非Object() 方法時,執行下面四步

1. var obj = {}; //生成一個object物件
2. obj.constructor = func; //設定constructor,由此判斷物件的型別由constructor屬性決定
3. obj.__proto__ = func.prototype; //這一步很關鍵,將obj連入原型鏈,從而能訪問func.prototype物件以及它的原型鏈上端物件的屬性
4. func.call( obj ); //為obj新增func()方法中宣告的屬性

__proto__prototype

這裡我把__proto__放在前面,因為它是原型鏈的基礎。

People.prototype.hobby = "籃球";
People.prototype.swim = function(){ console.log( "swimming" ) };
var obj = {};
obj.__proto__ = People.prototype;
console.log( obj.hobby );
obj.swim;

物件訪問屬性的順序是先查詢自身,顯然 hobbyswim 都不是obj自身的屬性,這是系統會查詢obj.__proto__指向的物件是否有這兩個屬性,這裡就是 People.prototype ,如果還沒有,會繼續找People.prototype.__proto__指向的物件有沒有所需屬性,這裡有一個知識點,宣告一個函式People()時,People.prototype 屬性指向誰呢?
根據我的研究,函式的prototype屬性的初始化很簡單,分為兩步

 1. People.prototype = {}; //將一個object物件賦給prototype
 2. People.prototype.constructor = People; //設定constructor為People,表示原型物件的型別是People

因為People.prototype是一個普通的object物件,所以有
People.prototype.__proto__ 指向 Object.prototype ,
這與圖中的描述是吻合的。
如此一來,下面的原型鏈就形成了
obj  __proto__  People.prototype  __proto__  Object.prototype __proto__  null
有一條對原型鏈文章的評價:js物件其實就是鍵值對,說的不錯,js中的方法和變數都以物件的形式存在,一個物件能訪問到的其實就是 自己的屬性原型鏈上的屬性
看看下面的例子

People.prototype.hobby = "籃球";
People.prototype.swim = function(){ console.log( "swimming" ) };
var obj = {};
obj.__proto__ = People.prototype;
//console.log( obj.hobby );
//obj.swim;
obj.hobby = "撩妹";
console.log( People.prototype.hobby );

發現結果仍然是 籃球,說明這裡是為obj添加了自己的hobby屬性,並沒有改動原型物件的屬性。
我們再做進一步測試

People.prototype.sing = function(){ console.log( this.song ) }

var obj = {};
obj.song = "愛的供養";
obj.__proto__ = People.prototype;
obj.sing();
People.prototype.sing.call( obj );
//結果
//愛的供養*2

這一步解釋了原型物件中的方法是怎麼被執行的。


這個圖解釋了原型鏈的原理,放在這裡供大家參考

FunctionObject

圖中 FunctionObject 的關係比較令人費解,其實可以這麼理解。
js中所有的物件都是由函式通過new操作符生成的,比如
var obj = new People()
則一定有obj.__proto__ == People.prototype'
而所有的函式物件都是由 Function() 函式生成的,Object()方法也不例外,所以有
Object.__proto__ = Function.prototype
當然,Function()方法也是由自己生成的,所以推出
Function.__proto__ = Function.prototype
(其實Object()方法和Function()方法都是 原生代碼(native code),也就是事先寫好的,並不是生成的物件,原型鏈中這樣設計是為了邏輯的完備)。
這裡比較特殊的兩個物件是 function.prototypeobject.prototype,這兩個物件的生成不符合上面提到的規律

1. People.prototype = {}; 
2. People.prototype.constructor = People; 

function.prototype 作為Function的原型物件是由Function()函式直接返回的,並不是一個object物件,而且這個屬性是隻讀的,它的值顯示如下
function Empty() {}
從邏輯上來說, 所有的函式物件也應該能訪問 Object.prototype 的屬性,所以有
function.protototype.__proto__ = Object.prototype
讓所有的函式物件通過 function.prototype 訪問原型鏈的末端 Object.prototype
作為原型鏈的尾端, Object.prototype 的屬性可以被js中的所有物件訪問, Object.prototype__proto__是隻讀的,這保證了 Object.prototype 作為原型鏈的一個出口。

結尾

希望能對讀者朋友們有所啟發,有想法請留言與我交流,轉載請註明作者和地址,謝謝!