面向?qū)ο?2 繼承

3.繼承

3.1 原型鏈

原型鏈?zhǔn)菍崿F(xiàn)繼承的主要方法。其基本思想是利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。

構(gòu)造函數(shù)、原型和實例的關(guān)系

每個構(gòu)造函數(shù)都有一個原型對象,原型對象都包含一個指向構(gòu)造函數(shù)的指針,而實例都包含一個指向原型對象的內(nèi)部指針。

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

function SubType(){
    this.subproperty = false;
}
// 使SubType繼承SuperType的實例 本質(zhì)是重寫原型對象
SubType.prototype = new SuperType();
SubType.prototype.getValue = function(){
    return this.subproperty;
}
var instance = new SubType();
alert(instance.getSuperValue());//true

image

注意 :==instance.constructor現(xiàn)在指向的是SuperType,這是因為原來SubType的原型指向了另一個對象--SuperType的原型,而這個原型對象的constructor屬性指向的是SuperType==

默認(rèn)的原型 Object
image
確定原型與實例的關(guān)系
  • instanceof:用這個操作符來測試實例與原型鏈中出現(xiàn)過的構(gòu)造函數(shù),結(jié)果就會返回true
alert(instance instanceof Object); //true
alert(instance instanceof SuperType); //true
alert(instance instanceof SubType); //true
  • 使用isPrototypeOf()方法,只要是原型鏈中出現(xiàn)的原型,都可以說是該原型鏈所派生的實例的原型
alert(Object.prototype isPrototypeOf(instance)); //true
alert(SuperType.prototype isPrototypeOf(instance));
alert(SubType.prototype isPrototypeOf(instance));
通過原型鏈實現(xiàn)繼承時,不能使用對象字面量創(chuàng)建原型方法,因為這樣會重寫原型鏈
function SuperType(){
    this.property = true;
}
SuperType.prototype.getSuperValue = function(){
    return this.property;
}

function SubType(){
    this.subproperty = false;
}
SubType.prototype = new SuperType();
// 使用對象字面量創(chuàng)建原型方法,會重寫原型鏈
SubType.prototype = {
    getSubValue: function(){
        return this.subproperty;
    }
};
var instance = new SubType();
alert(instance.getSuperValue());//error!!
原型鏈的問題
  • 包含引用類型值的原型屬性會被所有實例共享,因此在構(gòu)造函數(shù)中定義屬性而不是在原型對象中定義屬性。
    在通過原型來實現(xiàn)繼承時,原型實際上會變成另一個類型的實例。于是,原先的實例屬性也就變成了現(xiàn)在的原型屬性了。
function SuperType(){
    this.color = ["red", "blue", "green"];
    this.name = "Nicholas";
}
function SubType(){
}
SubType.prototype = new SuperType();

var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red","blue","green","black"
instance1.name = "Tom";
alert(instance1.name); //"Tom"

var instance2 = new SubType();
// colors是引用類型,會被所有實例共享
alert(instance2.colors); // "red","blue","green","black"
alert(instance2.name); //"Nicholas"
  • 沒有辦法在不影響所有對象實例的情況下,給超類型的構(gòu)造函數(shù)傳遞參數(shù)
function SuperType(name){
    this.name = name;
}
function SubType(name){
}
// ??如何向超類型的構(gòu)造函數(shù)傳遞參數(shù)??
SubType.prototype = new SuperType();

var instance2 = new SubType(name);
  • 綜合以上情況,實踐中很少會單獨(dú)使用原型鏈

3.2 借用構(gòu)造函數(shù)

在子類型構(gòu)造函數(shù)的內(nèi)部調(diào)用超類型構(gòu)造函數(shù)。

function SuperType(){
    this.colors=["red","blue","green"];
}
function SubType(){
    //
    superType.call(this);
}

var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); // "red,blue,green,black"

bar instance2 = new SubType();
alert(instance2.colors); //"red,blue,green"
使用這種方式,可以在子類型構(gòu)造函數(shù)中向超類型構(gòu)造函數(shù)傳遞參數(shù)。
function SuperType(name){
    this.name=name;
}
function SubType(){
    Super.call(this,"Nicholas");
    this.age = 29;
}

var instance = new SubType();
alert(instance.name);//"Nicholas"
  • 相關(guān)問題:

會出現(xiàn)與構(gòu)造函數(shù)模式相同的問題——方法都在構(gòu)造函數(shù)中定義,函數(shù)復(fù)用就無從談起了。而且在超類型的原型中定義的方法,對于子類型也是不可見的。

3.3 組合繼承——JavaScrip最常用的繼承模式

使用原型鏈實現(xiàn)對原型屬性和方法的繼承(通過在原型上定義方法實現(xiàn)了函數(shù)復(fù)用),通過借用構(gòu)造函數(shù)來實現(xiàn)對實例屬性的繼承(保證每個實例都有自己的屬性,而且可以向超類的構(gòu)造函數(shù)傳遞參數(shù))。

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

SuperType.prototype.sayName = function(){
    alert(this.name);
};

function SubType(name,age){
    SuperType.call(this,name);
    this.age = age;
}
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function(){
    alert(this.age);
};

var instance1 = new SubType("Nicholas",29);
instance1.sayName(); //"Nicholas"
instance1.sayAge(); //29

var instance2 = new SubType("Greg",27);
instance2.sayName(); //"Greg"
instance2.sayAge(); //27

3.4 原型式繼承

借助原型可以基于已有的對象創(chuàng)建新對象,同時還不必因此創(chuàng)建自定義類型。

function object(o){
    // 臨時的構(gòu)造函數(shù)
    function F(){}
    // 將傳入的對象作為這個構(gòu)造函數(shù)的原型
    F.prototype = o;
    // 返回新實例
    return new F();
}
var person = {
    name:"Nicholas",
    friends:["shelby","court","van"]
};
// 以person為原型創(chuàng)建一個新實例
var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");

var yetAnotherPerson = object(person);
yetAnotherPerson.name = "linda";
yetAnotherPerson.friends.push("barbie");

alert(person.friends); //"shelby,court,van,rob,barbie"

person.friends不僅屬于person所有,而且也會被anotherPerson、yetAnotherPerson共享。

ECMAScript5通過Object.create()方法規(guī)范化了原型式繼承,這個方法接受兩個參數(shù):一個用作新對象原型的對象,另一個(可選的)為新對象定義額外屬性的對象。只傳入一個參數(shù)的情況,和object()方法相同。

// Object.create()只用一個參數(shù)
var person = {
    name: "Nicholas",
    friends: ["shelby","court","van"]
};
var anotherPerson = Object.create(person);
// Object.create()用兩個參數(shù)
var person = {
    name: "Nicholas",
    friends: ["shelby","court","van"]
};
var anotherPerson = Object.create(person, {
    name:{
        value: "Greg"
    }
});
alert(anotherPerson.name); //Greg

var yetAnotherPerson = Object.create(person, {
    // 為新對象定義新的friends屬性,該屬性會覆蓋原型屬性中的friends
    friends:{
        value: ["1","2","3"]
    }
});
alert(yetAnotherPerson.friends); //"1,2,3"
// 但是原型對象中的friends屬性仍然被共享
alert(person.friends); //"shelby,court,van"
alert(anothorPerson.friends); //"shelby,court,van"

3.5 寄生式繼承

function createAnother(original){
    var clone = object(original);
    // 不能做到函數(shù)復(fù)用,導(dǎo)致效率降低
    // 添加新函數(shù),增強(qiáng)對象
    clone.sayHi = function(){
        alert("hi");
    }
    return clone;
}

var person = {
    name: "Nicholas",
    friends: ["shelby","court","van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"

經(jīng)試驗
使用原型式繼承方式出現(xiàn)如下問題

function object(o){
    function F(){}
    F.sayHi = function(){
        alert("Hi");
    };
    F.prototype = o;
    return new F();
}
var person = {
    name:"Nicholas",
    friends:["shelby","court","van"]
};

var anotherPerson = object(person);
anotherPerson.sayHi(); 
會輸出錯誤 VM6988:18 Uncaught TypeError: anotherPerson.sayHi is not a function

但是這種方式?jīng)]有問題

function object(o){
    function F(){}
    F.prototype = o;
    // 返回新實例
    F.prototype.sayHi=function(){
        alert("Hi");
    }
    return new F();
}
var person = {
    name:"Nicholas",
    friends:["shelby","court","van"]
};
// 以person為原型創(chuàng)建一個新實例
var anotherPerson = object(person);
anotherPerson.sayHi(); //"Hi"
function object(o){
    function F(){
        this.sayHi = function(){
            alert("Hi");
        }
    }
    F.prototype = o;
    // 返回新實例
    
    return new F();
}
var person = {
    name:"Nicholas",
    friends:["shelby","court","van"]
};
// 以person為原型創(chuàng)建一個新實例
var anotherPerson = object(person);
anotherPerson.sayHi(); //"Hi"

原因分析

function F(){}
F.sayHi = function(){
        alert("Hi");
    };

2017.03.15 F.sayHi是定義了“類”方法,因此創(chuàng)建的對象是沒有該方法的

3.6 寄生組合式繼承

組合繼承無論在什么情況下,都會調(diào)用兩次超類型構(gòu)造函數(shù):一次是在創(chuàng)建子類型原型的時候,另一次是在子類型構(gòu)造函數(shù)內(nèi)部。

function SuperType(name){
    this.name=name;
    this.colors=["red","blue","green"];
}
SuperType.prototype.sayName = function(){
    alert(this.name);
};
function SubType(name,age){
    SuperType.call(this,name);   //第二次調(diào)用SuperType()
    this.age=age;
}
SubType.prototype = new SuperType(); //第一次調(diào)用SuperType()

第一次調(diào)用SuperType構(gòu)造函數(shù)時,SubType.prototype(SubType的原型)會得到兩個屬性:name和colors。當(dāng)調(diào)用SubType構(gòu)造函數(shù)時,又會調(diào)用一次SuperType構(gòu)造函數(shù),這一次又在新對象上創(chuàng)建了實例屬性name和colors。于是這兩個屬性就屏蔽了原型中的兩個同名屬性

Markdown

為了避免創(chuàng)建兩次相同的屬性,解決方法——寄生組合式繼承。其基本思想是:不必為了指定子類型的原型而調(diào)用超類型的構(gòu)造函數(shù),我們所需要的無非就是超類型原型的一個副本而已。

function inheritPrototype(subType, superType){
    var prototype = object(superType.prototype);
    prototype.constructor = subType;
    subType.prototype = prototype;
}

function SuperType(name){
    this.name=name;
    this.colors = ["red","blue","green"];
}
SuperType.prototype.sayName=function(){
    alert(this.name);
};
function SubType(name,age){
    SuperType.call(this,name);
    this.age=age;
}
inheritPrototype(SubType,SuperType);
SubType.prototype.sayAge=function(){
    alert(this.age);
};

上訴例子只調(diào)用了一次SuperType構(gòu)造函數(shù),因此避免了在SubType.prototype上面創(chuàng)建不必要的、多余的屬性。寄生組合式繼承是引用類型最理想的繼承范式
相比之下,第二段程序少了SubType.prototype = new SuperType();這使得SubType.prototype中沒有了name和colors屬性,實現(xiàn)了避免了在SubType.prototype上面創(chuàng)建不必要的、多余的屬性的目的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1.繼承(接口繼承和實現(xiàn)繼承) 繼承是 OO 語言中的一個最為人津津樂道的概念。許多 OO 語言都支持兩種繼承方式...
    believedream閱讀 1,057評論 0 3
  • 博客內(nèi)容:什么是面向?qū)ο鬄槭裁匆嫦驅(qū)ο竺嫦驅(qū)ο缶幊痰奶匦院驮瓌t理解對象屬性創(chuàng)建對象繼承 什么是面向?qū)ο?面向?qū)ο?..
    _Dot912閱讀 1,536評論 3 12
  • 本章內(nèi)容 理解對象屬性 理解并創(chuàng)建對象 理解繼承 面向?qū)ο笳Z言有一個標(biāo)志,那就是它們都有類的概念,而通過類可以創(chuàng)建...
    悶油瓶小張閱讀 963評論 0 1
  • 天真,天真? 什么時候,大家對于天真是何其的贊賞,又如今,天真似乎開始變成了“傻B”的代名詞 你有被別人評價過嗎?...
    心在baby路上閱讀 213評論 0 0
  • 在那遙遠(yuǎn)的地方 有位美麗的女郎 哦,我心愛的姑娘 我夢見你的臉龐 瞧瞧你的衣裳 你不是我的新娘 我不是你的情郎 不...
    狼吻與蝶花閱讀 165評論 0 1

友情鏈接更多精彩內(nèi)容