1. 程式人生 > >JS 原型以及原型鏈

JS 原型以及原型鏈

 

    /**
     * 原型物件
     * 無論什麼時,只要建立一個新函式,就會根據一組特定的規則為該函式建立一個 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 { … } 空物件