js最經常使用和最有效的繼承

1 前言

我試圖用簡單、清晰、專業的方式講清楚js繼承這麼一個有點複雜的問題javascript

2 繼承的精髓

2.1 爲何要繼承?

由於須要複用java

2.2須要複用什麼?該如何複用?

經過複用咱們能夠實現共享。函數

從對象的角度來講,咱們不想重複寫同一段邏輯,因此邏輯須要複用;可是咱們不但願一個對象掌管的變量被其它對象修改。因此變量不能共享。也就是要共享函數,不共享屬性。this

3 js中的繼承

3.1 概述

原型鏈能夠用來實現共享,構造函數可讓對象擁有本身的屬性,二者結合的組合的組合繼承即是最經常使用的繼承方式。spa

組合繼承中有一個弊端,經過簡單的改進,寄生組合式繼承能夠實現最有效的繼承。接下來將展開講解組合繼承寄生組合式繼承prototype

3.2 預備知識

若是已經瞭解了這些知識,能夠直接跳過設計

3.2.1 構造函數、實例和原型的關係
function Person () {
      
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job="Software Engineer";
Person.prototype.sayName=function () {
    console.log(this.name);
};
var person1=new Person();
person1.sayName();//Nicholas

構造函數(Person)能夠用來建立實例(person1),建立的實例和構造函數共同指向一個原型(Person.prototype),原型的構造器又指向了構造函數。
若是沒有指定原型,則默認的原型爲Object.prototypecode

clipboard.png

  • new操做符具體幹了什麼呢?對象

  • 建立一個空對象,而且 this 變量引用該對象,同時還繼承了該函數的原型。var obj = {};blog

  • 屬性和方法被加入到 this 引用的對象中。obj.__proto__ = Base.prototype; (圖示中用[[Prorotype]]表明__proto__)

  • 新建立的對象由 this 所引用,而且最後隱式的返回 this 。Base.call(obj);

3.2.2 原型鏈的屏蔽和動態追加

屏蔽:

//...上面的代碼,此處省略
var person2=new Person();
person1.name='Mike';
person1.sayName();//Mike
person2.sayName();//Nicholas

clipboard.png

屬性值沿着原型鏈,遵循就近原則。就近的屬性值會屏蔽上層原型鏈的屬性值

動態追加而非斷開:

誤區:javaScript高級程序設計第三版 中指出了 修改原型時 這樣一種斷開情景

function Person () {
    
}
var friend=new Person();
Person.prototype={
    constructor:Person,
    name:'Nicholas',
    age:29,
    job:'Software Engineer',
    sayName:function () {
        console.log(this.name);
    }
}
friend.sayName();//error

clipboard.png

friend的原型丟失了,和它斷開了聯繫。

其實如今已經有所改進,運行friend.sayName();,應該輸出爲:Nicholas,當修改原型時,語言的設計意圖是什麼?若是你是語言的設計者到底應該爲什麼種輸出?

我認爲最好的效果是確保其穩妥性的同時,又不失靈活是最佳的,在這個例子中,建立完原型關係後又想追加原型中的屬性,我但願他可以追加成功,所以,但願它輸出爲:Nicholas

可是,我不但願原型中的屬性隨意修改,先開始的時候我給他賦予了值,也定義了相應的方法使用這個值,若是這個值在後期隨意修改的話,使用這個方法的時候會以爲不知所措。

因此我但願的涉及是原型屬性值能夠追加但定義後值不能隨意修改,以下:

function Person () {
    
}
Person.prototype.name = "Greg";
Person.prototype.age = 24;
Person.prototype.job="Doctor";
var friend=new Person();
Person.prototype={
    constructor:Person,
    name:'Nicholas',
    age:29,
    job:'Software Engineer',
    sayName:function () {
        console.log(this.name);
    }
}
friend.sayName();//Greg

打印friend.__proto__

Object {name: "Greg", age: 24, job: "Doctor"}
age:24
job:"Doctor"
name:"Greg"
sayName:function()

3.2.3 總結

橫向上,每一個原型的屬性 在原型自己被修改時 只是動態追加而不會修改原值;縱向上,原型鏈繼承遵循就近原則。

3.3 組合繼承

3.3.1 原型鏈繼承

讓一個構造函數A的原型爲另外一個構造函數B的實例,便造成了A繼承B的原型鏈。

function SuperType() {
    this.property=true;
}
SuperType.prototype.getSuperValue=function () {
    return this.property;
};
function SubType() {
    this.subproperty=false;
};
SubType.prototype=new SuperType();
SubType.prototype.getSubValue=function () {
    return this.subproperty;
}
var instance=new SubType();

clipboard.png

  • 不易理解的地方:subproperty的屬性在實例中而不在SubType構造函數中;property的屬性在 SubType.prototype中而不在SuperType構造函數中。

  • 講解:subproperty和property都是實例屬性,在哪裏建立了實例就出如今哪裏,

優點:函數共享
缺陷:原型鏈體現出了共享的特性,當一個實例改變了其從原型那裏繼承來的引用屬性值時,其它繼承自這個原型屬性的值都將被改變。

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

function SubType() {
};
SubType.prototype=new SuperType();
var instance1=new SubType();
instance1.colors.push('black');
console.log(instance1.colors);//["red", "blue", "green", "black"]
var instance2=new SubType();
console.log(instance2.colors);//["red", "blue", "green", "black"]
3.3.2 構造函數繼承
function SuperType(name) {
    this.name=name;
}
function SubType() {
    SuperType.call(this,'Nicholas');
    this.age=29;
}
var instance=new SubType();
console.log(instance.name+' '+instance.age);//Nicholas 29

構造函數每一個實例的屬性都藉助構造函數本身生成

優點:每一個實例屬性各自獨立
缺陷:沒法共享函數

3.3.3 組合繼承

組合繼承結合了原型鏈繼承和構造函數繼承的優點:

function SuperType(name) {
    this.name=name;
    this.colors=['red','blue','green']
}
SuperType.prototype.sayName=function () {
    console.log(this.name);
}
function SubType(name,age) {
    SuperType.call(this,name);
    this.age=age;
}
SubType.prototype=new SuperType();
SubType.prototype.sayAge=function () {
    console.log(this.age);
}
var instance1=new SubType('Nicholas',29);
instance1.colors.push('black');
console.log(instance1.colors);
instance1.sayName();
instance1.sayAge();
var instance2=new SubType('Greg',27);
console.log(instance2.colors);
instance2.sayName();
instance2.sayAge();

clipboard.png

3.4 寄生組合式繼承

組合繼承雖然實現了需求:共享函數,但不共享屬性,但是它是有不足之處:咱們在獨立屬性時只是但願實例有各自的屬性就行了,不須要原型(SubType.prototype)中也存在屬性,這就多餘了。

SubType.prototype存在屬性是由於它對SuperType作了實例化繼承,咱們將實例話繼承換成前拷貝繼承即可以解決問題:

//將組合繼承中的實例化繼承:
//SubType.prototype=new SuperType();
//改成淺拷貝繼承:
function inheritPrototype(subType,superType){
    var prototype=Object.create(superType.prototype);//建立對象
    prototype.constructor=subType;
    subType.prototype=prototype;
}
inheritPrototype(SubType,SuperType);

Object.create()代碼說明:

function Object.create(o){
    function F() {};
    F.prototype=o;
    return new F();
}

參考

javascript高級程序設計 第三版

相關文章
相關標籤/搜索