首頁  >  文章  >  web前端  >  javascript原型鏈需要注意的地方的總結

javascript原型鏈需要注意的地方的總結

不言
不言轉載
2018-10-20 15:59:551668瀏覽

這篇文章帶給大家的內容是關於javascript原型鏈需要注意的地方的總結,有一定的參考價值,有需要的朋友可以參考一下,希望對你有所幫助。

前言:最近在細讀Javascript高階程式設計,對我而言,中文版,書中很多地方翻譯的差強人意,所以用自己所理解的,試著解讀下。如有紕漏或錯誤,會非常感謝您的指出。文中絕大部分內容引用自《JavaScript高級程式設計第三版

1. 別忘記預設的原型

事實上,前面範例中展示的原型鏈還少一環。

我們都知道, 所有引用類型預設都繼承了Object,而這個繼承也是透過原型鏈實現的。

所有函數的預設原型是Object的實例。因為函數的原型物件也是物件嘛!物件當然是Object的實例咯!

因此函數的原型都會包含一個內部指標(__proto__), 指向Object.prototype。

這也是所​​有自訂類型都會繼承toString()、valueOf()等預設方法的根本原因。

所以,上篇範例中所展示的原型的原型鏈中也應該包含另一個繼承層次。

以下程式碼展示了這個完整的原型鏈。

//完整原型链的伪代码
function Object() {
}
Object.prototype = {
    constructor: f Object(),
    hasOwnProperty: f hasOwnProperty(),
    isPrototypeOf: f isPrototypeOf(),
    propertyIsEnumerable: f propertyIsEnumerable(),
    toLocaleString: f toLocaleString(),
    toString: f toString(),
    valueOf: f valueOf()
}
//SuperType 父类型
function SuperType(){
    this.property = true;
}
SuperType.prototype.getSuperProperty = function() {
    console.log(this.property);
    return this.property;
}
/*
SuperType.prototype = {
    constructor: f SuperType(),
    getSuperProperty: function() {
    console.log(this.property);
    return this.property;
    }, 
    __proto__ : {
        constructor: f Object(),
        hasOwnProperty: f hasOwnProperty(),
        isPrototypeOf: f isPrototypeOf(),
        propertyIsEnumerable: f propertyIsEnumerable(),
        toLocaleString: f toLocaleString(),
        toString: f toString(),
        valueOf: f valueOf()
    }
}
*/
//SubType 子类型
function SubType() {
    this.subproperty = false;
}
//子类型 继承 父类型
SubType.prototype = new SuperType();
//实际上子类型的原型是这样的。
/*SubType.prototype = {
    property: true,
    __proto__:  {
        constructor : SuperType,
        getSuperProperty:function() {
            console.log(this.property);
            return this.property;
        }
    }
}
*/
SubType.prototype.getSubProperty = function(){
    console.log(this.subproperty);
    return this.subproperty;
}
//那么现在子类型的原型对象是这样的
/*SubType.prototype = {
    property: true,
    getSubProperty: function()  {
    console.log(this.subproperty);
    return this.subproperty;
    },
    __proto__:  {
        constructor : SuperType,
        getSuperProperty:function() {
            console.log(this.property);
            return this.property;
        }
    }
}
*/

var subInstanceObject = new SubType();
console.log(subInstanceObject.getSuperProperty()); // true

一句話,SubType(子型別)繼承了SuperType(父型別),

而SuperType(父型別)繼承了Object(祖先)。

當呼叫subInstanceObject.toString()時,實際上呼叫的是在儲存在Object.prototype中的那個方法。

2. 確定原型和實例物件關係

可以透過兩種方式來確定原型和實例之間的關係。

第一種方式是使用instanceof操作符,只要偵測到的實例物件中的原型鏈包含出現過的建構函數,結果就會傳回true。
因為,這說明他們都參與了,實例物件的創建。

console.log(subInstanceObject instanceof Object); // true
console.log(subInstanceObject instanceof SuperType); // true
console.log(subInstanceObject instanceof SubType); // true

由於原型鏈的關係, 我們可以說subIntanceObject是Object、SuperType或SubType中任何一個類型的實例。

第二種方式是使用isPrototypeOf()方法。同樣,只要是原型鏈中出現過的原型,都可以說該原型鏈所衍生的實例物件的原型。

console.log(Object.prototype.isPrototypeOf(subInstanceObject)); //true
console.log(SuperType.prototype.isPrototypeOf(subIntanceObject)); // true
console.log(SubType.prototype.isPrototypeOf(subIntanceObject)); //true

3. 謹慎地定義方法

子類型有時候需要覆寫父類型的某個方法,或是需要新增父型別中不存在的某個方法。

但不管怎麼樣,給原型添加方法的程式碼一定要放在替換原型的語句之後。

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;
}

//重写 超类型中 的 方法
SubType.prototype.getSuperValue = function() {
    return false;
}

var instance = new SubType();
console.log(instance.getSuperValue())

以上程式碼中,第一個方法getSubValue()被加入了SubType中。
第二個方法getSuperValue()是原型中已經存在的方法。
重寫這個方法將會子類別的原型會找出屬於自己的getSuperValue()方法。
當透過SuperType的實例物件呼叫getSuperValue()時, 也會繼續呼叫原來的那個方法。

再次強調,必須在用SuperType的實例物件取代原型之後,再定義兩個方法。

還有一點需要提醒,在透過原型鏈實作繼承時,不能使用物件字面量來建立原型方法。這樣會重寫原型鏈的。

function SuperType(){
    this.property = true;
}

SuperType.prototype.getSuperValue = function(){
    return this.property;
}

function SubType(){
    this.subproperty = false;
}

//继承SuperType
SubType.prototype = new SuperType();

/* 
现在的原型
SubType.prototype = {

    property: true,
    __proto__: {
        constructor: SuperType,
        getSuperValue: function() {
            return this.property;
        }
    }
}
*/

//使用对象字面量语法会改写原型,导致上一行代码无效
// SubType.prototype = new Object();
SubType.prototype = {

    getSubValue: function() {
        return this.subproperty;
    },

    someOtherMethod: function () {
        return false;
    }

    /*,
    __proto__ : {
        constructor: fn Object(),
        .......
    }
    */

}

var instance =  new SubType();
console.log(instance.getSuperValue()); // error: instance.getSuperValue is not a function

以上程式碼展示了剛剛把SuperType的實例物件賦值給原型,緊接著又將原型替換成一個物件字面量而導致的問題。

因為SubType的原型其實保存的是一個Object的實例,而不是SuperType的實例對象,因此這條鍊子就斷了。

4. 原型鏈的問題

原型鏈雖然很強大,可以用它來實現繼承,但是總有缺點,世界上不存在萬全法。

最主要的問題來自包含引用類型值的原型。

包含引用類型值的原型屬性會被所有實例物件共用。

而這也正是組合使用原型模式和建構函式模式的原因。
在建構函式模式中定義屬性,在原型模式中定義共享的方法。

在透過原型來實現原型繼承時,原型實際上會變成另一個類型的實例物件。

原先的實例物件屬性,也就變成了現在的原型屬性了。

function SuperType() {
    this.colors = ['red', 'green', 'blue'];
}

function SubType() {
}

// 子类型继承父类型
SubType.prototype = new SuperType();

/*
SubType.prototype = {
    colors: ['red', 'green', 'blue'],
    __proto__: {
        constructor: fn SuperType(),
        .....
    }
}
*/

var instance1 = new SubType();

instance1.colors.push('black');

console.log(instance1.colors); // ['red', 'green', 'blue', 'black']

var instance2 = new SubType();

console.log(instance2.colors); // ['red', 'green', 'blue', 'black']

原型鏈的第二個問題在於, 沒有辦法在不影響所有實例物件的情況下,給父類型的建構函式傳遞參數。

由於上述兩個問題的存在,事件中很少會單獨使用原型鏈。

#

以上是javascript原型鏈需要注意的地方的總結的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:segmentfault.com。如有侵權,請聯絡admin@php.cn刪除