JavaScript高級程序設計學習筆記之繼承模式

原型鏈

JavaScript的繼承主要依靠原型鏈來實現(xiàn)的。我們知道,構(gòu)造函數(shù),原型,和實例之間的關(guān)系:每個構(gòu)造函數(shù)都有一個原型對象,原型對象都包含一個指向構(gòu)造函數(shù)的指針,而實例都包含一個原型對象的指針。

實現(xiàn)原型鏈的方式如下

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

function  SubType(){
    this.subpropertype=false;
}
//讓原型對象稱為另一個構(gòu)造函數(shù)的實例
SubType.prototype=new SuperType();

SubType.prototype.getSubValue=function(){
    return this.subpropertype;
};
var  instance=new SubType();
alert(instance.getSuperValue());//true
//這個實例繼承了SuperType.prototype的constructor屬性?
alert(instance.constructor==SuperType);//true

上述代碼繼承是通過SubType.prototype=new SuperType();來實現(xiàn),創(chuàng)建SuperType的實例,并將該實例賦給SubType.prototype。

繼承實現(xiàn)的本質(zhì)是重寫原型對象,代之以一個新類型的實例。

下圖為構(gòu)造函數(shù),實例以及原型之間的關(guān)系圖:

繼承.jpg

原型鏈頂端:所有引用類型都默認繼承Object,所以,所有函數(shù)的默認原型都是Object的實例,默認原型都會包含一個內(nèi)部指針[[prototype]],指向Object.prototype。

原型鏈缺點

  1. 實例屬性變?yōu)樵蛯傩?/li>

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

}
SubType.prototype = new SuperType();

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

var instance2 = new SubType();
alert(instance2.color);//"red,green,blue,black"

這個問題似曾相識,正是原型模式創(chuàng)建對象時由于共享引用類型屬性,導致牽一發(fā)動全身的問題。

  1. 在創(chuàng)建子類型時,不能向超類型的構(gòu)造函數(shù)傳遞參數(shù)。
    所以,單獨使用原型鏈情況較少。

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

針對原型鏈的第一個問題,我們可采用借用構(gòu)造函數(shù)的技術(shù)來解決?;舅枷刖褪窃谧宇愋蜆?gòu)造函數(shù)的內(nèi)部調(diào)用超類型構(gòu)造函數(shù)??蠢樱?/p>

function  SuperType(){
    this.color=["red","green","blue"];
}
function  SubType(){
    //繼承自SuperType
    SuperType.call(this);
}
var instance1 = new SubType();
instance1.color.push("black");
alert(instance1.color);//"red,green,blue,black"

var instance2 = new SubType();
alert(instance2.color);//"red,green,blue"

在新創(chuàng)建的SubType子類型的實例中調(diào)用SuperType超類型構(gòu)造函數(shù),就可以在新的實例對象上執(zhí)行SuperType()函數(shù)中定義的所有對象初始化代碼。問題不就解決了嗎!
但是,這種模式的缺點是在超類型中定義的方法,對子類型是不可見的,無法實現(xiàn)共享方法。
所以,這種方法也不常用。

組合繼承

組合上述兩種方法就是組合繼承。用原型鏈實現(xiàn)對原型屬性和方法的繼承,用借用構(gòu)造函數(shù)技術(shù)來實現(xiàn)實例屬性的繼承。無疑,集兩者之大成,這才是最常用的繼承模式??矗?/p>

function  SuperType(){
    this.name=name;
    this.color=["red","green","blue"];
}
SuperType.prototype.sayName = function(){
    alert(this.name);
}
function  SubType(name,age){
    //繼承了SuperType
    SuperType.call(this,name);
    //自己又添加了一個
    this.age = age;
}
//構(gòu)建原型鏈
SubType.prototype = new SuperType();
//重寫SubType.prototype的constructor屬性,指向自己的構(gòu)造函數(shù)SubType
SubType.prototype.constructor=SubType;
//原型方法,被實例們共享
SubType.prototype.sayAge = function(){
    alert(this.age);
}

var instance1 = new SubType("Nichola",29);
instance1.color.push("black");
alert(instance1.color);//"red,green,blue,black"
instance1.sayName();//"Nichola"
instance1.sayAge();//29

var instance2 = new SubType("Grey",24);
alert(instance2.color);//"red,green,blue"
instance2.sayAge();//24
instance2.sayName();//"Grey"

這個方案已經(jīng)看似perfect了。但是,后面再說。

原型式繼承

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

function object(o){     //返回一個對象以傳入對象為原型
    function F(){}
    F.prototype = o;
    return new F();
}

var person ={
    name:"Nichola",
    friends:["Shelly","Court","Van"]
};
var person1 = object(person);
person1.name = "Grey";
person1.friends.push("Rob");

var person2 = object(person);
person2.name = "Linda";
person2.friends.push("Barble");

alert(person.friends);//"Shelly,Court,Van,Grey,Barble"

使用場合:需求簡單,只需要讓新對象與已有對象保持相似。優(yōu)點,不必創(chuàng)建構(gòu)造函數(shù),缺點,包含引用類型值的屬性始終共享相應的值。
Object.create()正是為實現(xiàn)這種模式誕生。

寄生式繼承

與原型式繼承相似,也是基于某個對象或某些信息創(chuàng)建對象,然后增強對象,最后返回對象。實現(xiàn)方法:創(chuàng)建一個僅用于封裝繼承過程的函數(shù),該函數(shù)在內(nèi)部以某種方式來增強對象,最后返回這個對象。看!


function createAnother(original){
    var clone = object(original);//通過調(diào)用函數(shù)創(chuàng)建對象
    clone.sayHi= function (){    //增強對象
        alert("Hi");
    };
    return clone;//返回對象
}
//可以返回新對象的函數(shù)
function object(o){
    function F(){}
    F.prototype = o;
    return new F();
}

var person ={
    name:"Nichola",
    friends:["Shelly","Court","Van"]
};

var anotherPerson = createAnother(person);
anotherPerson.sayHi();//"Hi"

這種繼承模式適用的場合:任何返回新對象的函數(shù)都可以。缺點是不能做到函數(shù)復用。

寄生組合式繼承

上面說到組合繼承也有缺點,就是無論在何種情況下,都會調(diào)用兩次超類型構(gòu)造函數(shù),一次是在創(chuàng)建子類型原型時,還有一次是在子類型構(gòu)造函數(shù)內(nèi)部。
這種模式集中了寄生式和組合式繼承的優(yōu)點。

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

SubType.prototype.constructor=SubType;

SubType.prototype.sayAge = function(){
    alert(this.age);
}

var instance1 = new SubType("Nichola",29);

第一次調(diào)用SuperType():給SubType.prototype寫入兩個屬性name,color
第二次調(diào)用SuperType():給instance1寫入兩個屬性name,color
實例對象instance1上的兩個屬性就屏蔽了其原型對象SubType.prototype的兩個同名屬性。所以,組合模式的缺點就是在SubType.prototype上創(chuàng)建不必要的重復的屬性。
寄生組合式繼承基本模式:

function  inheritPrototype(SubType,SuperType){
    var prototype = object(superType.prototype);//創(chuàng)建對象
    prototype.constructor = SubType;//增強對象
    SubType.prototype = prototype;//制定對象
}

首先,創(chuàng)建超類型的一個副本;
其次,為副本添加constructor屬性,使其指向子類型構(gòu)造函數(shù);
最后,將副本賦值給子類型原型。

function  SuperType(){
    this.name=name;
    this.color=["red","green","blue"];
}
function  SubType(){
    SuperType.call(this.name);
    this.age = age;
}
function  inheritPrototype(SubType,SuperType){
    var prototype = object(superType.prototype);//創(chuàng)建對象
    prototype.constructor = SubType;//增強對象
    SubType.prototype = prototype;//制定對象
}
SubType.prototype.sayAge = function(){
    alert(this.age);
}

var instance1 = new SubType("Nichola",29);

借用構(gòu)造函數(shù)來繼承實例屬性,使用寄生式繼承來繼承超類型的原型,然后再將結(jié)果賦給子類型原型。這樣既可以繼承超類型的實例屬性,也可繼承超類型原型中的原型屬性。這是最優(yōu)解。

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

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

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