JS 原型以及原型鏈
阿新 • • 發佈:2018-11-05
/** * 原型物件 * 無論什麼時,只要建立一個新函式,就會根據一組特定的規則為該函式建立一個 prototype 屬性 * 這個屬性就是原型物件。預設情況下,所有原型物件都會自動獲得一個 constructor(建構函式) * 屬性,這個屬性包含一個指向 prototype 屬性所在函式的指標。當建構函式建立一個新例項後, * 該例項的內部包含一個指標[[Prototype]](內部屬性),指向建構函式的原型物件。 */ /** * 如: * 建立一個函式 A:function A(){} * function A(){} 會有一個屬性: prototype 屬性 * function A(){} 的原型物件為: A.prototype * function A(){} 的原型物件會自動獲得一個 constructor 屬性: A.prototype.constructor * 那麼: * function A(){} 中的 prototype 屬性 ---(指向)---> A.prototype (function A(){} 的原型物件) * A.prototype.constructor (constructor屬性) ---(指向)---> function A(){} (函式 A)*/ function A(){} console.log(A.prototype.constructor==A);//true 可見原型物件中的 constructor 指向函式A /** * 如: * 使用 new 關鍵字結合 A() 函式來建立例項 obj * 則 obj 這個例項會有一個 [[Prototype]] 內部屬性指向 A 的原型物件 A.prototype * Firefox、Safari 和Chrome 在每個物件上都支援一個屬性 __proto__ ,它就相當於 [[Prototype]]*/ var obj=new A(); console.log(obj.__proto__==A.prototype);//true 可見例項中的 [[Prototype]] 指向函式原型 A.prototype A.prototype.name="guang";//給A的原型物件增加一個name:"guang"屬性 A.prototype.sayName=function(){return this.name};//給A的原型物件增加一個sayName方法 var obj=new A(); console.log(obj.sayName());//guang
// 上圖展示了A 建構函式、A 的原型屬性以及A 現有的例項 obj之間的關係。 // 在此,A.prototype 指向了原型物件,而A.prototype.constructor 又指回了A。 // 原型物件中除了包含constructor 屬性之外,還包括後來新增的其他屬性。A 的每個例項— // 都包含一個內部屬性,該屬性僅僅指向了 A.prototype原型物件,而建構函式沒有直接的關係。 // 雖然這 obj例項不包含屬性和方法,但卻可呼叫 A.sayName(),這是通過查詢物件屬性的過程來實現的。 // 每當程式碼讀取某個物件的某個屬性時,都會執行一次搜尋,目標是具有給定名字的屬性。搜尋首先 // 從物件例項本身開始。如果在例項中找到了具有給定名字的屬性,則返回該屬性的值;如果沒有找到, // 則繼續搜尋指標指向的原型物件,在原型物件中查詢具有給定名字的屬性。如果在原型物件中找到了這 // 個屬性,則返回該屬性的值。 // 對於上面的例子而言,在呼叫A.sayName()的時候,會先後執行兩次搜尋 // 首先,解析器會問:“例項A 有sayName 屬性嗎?”答:“沒有。”然後,它繼續搜尋,再 // 問:“A 的原型有sayName 屬性嗎?”答:“有。”於是,它就讀取那個儲存在原型物件中的函式。 // 而這正是多個物件例項共享原型所儲存的屬性和方法的基本原理。
/** * 原型鏈 * 每個建構函式都有一個原型物件,原型物件都包含一個指向建構函式的指標,而例項都包含一個指向原型物件的內部指標。 * 當一個原型物件是另一個原型物件的例項時,該原型物件將包含一個指向另一個原型的指標。相應地,另一個原型中也包含著一個指向 * 另一個建構函式的指標。假如另一個原型又是另一個型別的例項,那麼上述關係依然成立,如此層層遞進,就構成了例項與原型的鏈條 */ function SuperType(){ this.property = true; } SuperType.prototype.getSuperValue = function(){ return this.property; }; function SubType(){ this.subproperty = false; } //繼承了SuperType SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function (){ return this.subproperty; }; var instance = new SubType(); console.log(instance.getSuperValue()); //true //以上程式碼定義兩個型別:SuperType、SubType。通過建立 SuperType的例項,原型物件 SubType.prototype 繼承了SuperType的屬性 //本質上是通過重寫原型物件實現繼承。如圖:
// 在上面的程式碼中,並沒有使用SubType 預設提供的原型,而是給它換了一個新原型;這個新原型 // 就是SuperType 的例項。於是,新原型不僅具有作為一個SuperType 的例項所擁有的全部屬性和方法, // 而且其內部還有一個指標,指向了SuperType 的原型。 // 最終結果就是這樣的:instance 指向SubType的原型, SubType 的原型又指向SuperType 的原型。 // getSuperValue() 方法仍然還在 SuperType.prototype 中,但property 則位於SubType.prototype 中。 // 這是因為property 是一個例項屬性,而getSuperValue()則是一個原型方法。既然SubType.prototype 現在是SuperType // 的例項,那麼property 當然就位於該例項中了。此外,要注意instance.constructor 現在指向的 // 是SuperType,這是因為SubType 的原型指向了另一個物件——>SuperType 的原型,而這個原型物件的constructor 屬性指向的是SuperType。 // 在通過原型鏈實現繼承的情況下,搜尋過程就得以沿著原型鏈繼續向上。就拿上面的例子來說,呼叫 // instance.getSuperValue()會經歷三個搜尋步驟:1>搜尋例項;2>搜尋SubType.prototype;3>搜尋SuperType.prototype, // 4>最後一步才會找到該方法。在找不到屬性或方法的情況下,搜尋過程總是要一環一環地前行到原型鏈末端才會停下來。
// 事實上,因為所有引用型別預設都繼承了Object,而這個繼承也是通過原型鏈實現的。所有函式的預設原型都是Object 的例項, // 因此預設原型都會包含一個內部指標,指向Object.prototype。這也正是所有自定義型別都會繼承toString()、valueOf()等 // 預設方法的根本原因。對該例子來說完整的原型鏈應該如下:
//內建函式及其原型鏈
//上述圖中的原型及原型鏈的關係圖中, 將 String 換成 Array、Number、Boolean、及其他一些內建函式時,依然有類似的關係 //下面是與之相關的一些執行結果: console.log("XXX"); console.log("String:",String);//String: function String() 內建建構函式 console.log("Function:",Function);//Function: function Function() 內建建構函式 console.log("Object:",Object);//Object: function Object() 內建建構函式 console.log("Array:",Array);//Array: function Array() 內建建構函式 console.log("Number:",Number);//Number: function Number() 內建建構函式 console.log("Boolean:",Boolean);//Boolean: function Boolean() 內建建構函式 console.log("XXX.prototype"); console.log("String.prototype:",String.prototype);//String.prototype: String { "" } 空字串 console.log("Function.prototype:",Function.prototype);//Function.prototype: function () 空函式 console.log("Object.prototype:",Object.prototype);//Object.prototype: Object { … } 空物件 console.log("Array.prototype:",Array.prototype);//Array.prototype: Array [] 空陣列 console.log("Number.prototype:",Number.prototype);//Number.prototype: Number { 0 } 數字零 console.log("Boolean.prototype:",Boolean.prototype);//Boolean.prototype: Boolean { false } 布林值 false console.log("XXX.prototype.constructor"); console.log("String.prototype.constructor:",String.prototype.constructor); //String.prototype.constructor: function String() 內建建構函式 console.log("Function.prototype.constructor:",Function.prototype.constructor); //Function.prototype.constructor: function Function() 內建建構函式 console.log("Object.prototype.constructor:",Object.prototype.constructor); //Object.prototype.constructor: function Object() 內建建構函式 console.log("Array.prototype.constructor:",Array.prototype.constructor); //Array.prototype.constructor: function Array() 內建建構函式 console.log("Number.prototype.constructor:",Number.prototype.constructor); //Number.prototype.constructor: function Number() 內建建構函式 console.log("Boolean.prototype.constructor:",Boolean.prototype.constructor); //Boolean.prototype.constructor: function Boolean() 內建建構函式 console.log("XXX.__proto__"); console.log("String.__proto__:",String.__proto__);//String.__proto__: function () 空函式 console.log("Function.__proto__:",Function.__proto__);//Function.__proto__: function () 空函式 console.log("Object.__proto__:",Object.__proto__);//Object.__proto__: function () 空函式 console.log("Array.__proto__:",Array.__proto__);//Array.__proto__: function () 空函式 console.log("Number.__proto__:",Number.__proto__);//Number.__proto__: function () 空函式 console.log("Boolean.__proto__:",Boolean.__proto__);//Boolean.__proto__: function () 空函式 console.log("XXX.prototype.__proto__"); console.log("String.prototype.__proto__:",String.prototype.__proto__); //String.prototype.__proto__: Object { … } 空物件 console.log("Function.prototype.__proto__:",Function.prototype.__proto__); //Function.prototype.__proto__: Object { … } 空物件 console.log("Object.prototype.__proto__:",Object.prototype.__proto__); //Object.prototype.__proto__: null 空物件 console.log("Array.prototype.__proto__:",Array.prototype.__proto__); //Array.prototype.__proto__: Object { … } 空物件 console.log("Number.prototype.__proto__:",Number.prototype.__proto__); //Number.prototype.__proto__: Object { … } 空物件 console.log("Boolean.prototype.__proto__:",Boolean.prototype.__proto__); //Boolean.prototype.__proto__: Object { … } 空物件 console.log("self defined function"); function test(){} console.log("test:",test);//test: function test() 自定義函式 console.log("test.prototype:",test.prototype);//test.prototype: Object { … } 空物件 console.log("test.prototype.constructor:",test.prototype.constructor); //test.prototype.constructor: function test() 自定義函式 console.log("test.__proto__:",test.__proto__);//test.__proto__: function () 空函式 console.log("test.prototype.__proto__:",test.prototype.__proto__); //test.prototype.__proto__: Object { … } 空物件