我對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;
物件訪問屬性的順序是先查詢自身,顯然 hobby 和 swim 都不是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
有一條對原型鏈文章的評價: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
這一步解釋了原型物件中的方法是怎麼被執行的。
這個圖解釋了原型鏈的原理,放在這裡供大家參考
Function與Object
圖中 Function 與 Object 的關係比較令人費解,其實可以這麼理解。
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.prototype 和 object.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 作為原型鏈的一個出口。
結尾
希望能對讀者朋友們有所啟發,有想法請留言與我交流,轉載請註明作者和地址,謝謝!