《javascript高級程序設計》筆記:繼承

繼承和前面兩篇文章中的知識很是相關,若是對函數建立原理原型鏈不熟悉,請猛戳:
《javascript高級程序設計》筆記:建立對象
《javascript高級程序設計》筆記:原型圖解javascript

繼承,通俗的說,就是將自身不存在的屬性或方法,經過某種方式爲本身所用html

文章分別介紹原型鏈繼承、call/apply繼承(借用構造函數繼承)、組合繼承、原型式繼承、寄生式繼承、寄生組合式繼承java

1. 原型鏈繼承

核心:將父類的實例做爲子類的原型segmentfault

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();
alert(instance.getSuperValue());//true

簡單的原型鏈分析app

clipboard.png

《javascript高級程序設計》筆記:建立對象 的文章中,使用原型建立對象會存在多個實例對引用類型的操做會被篡改的問題,在上面一樣存在這個問題,以下:函數

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

SubType.prototype = new SuperType();

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

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

兩個實例對象instance1和instance2的colors屬性指向相同,改變一個會影響另外一個實例的屬性性能

缺陷:
(1)原型鏈繼承多個實例的引用類型屬性指向相同,存在篡改的可能
(2)不能傳遞參數this

2. 借用構造函數繼承

核心:使用父類的構造函數來加強子類實例,等同於複製父類的實例給子類(不使用原型)spa

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

function SubType(name, age){
  // 繼承自SuperType
  SuperType.call(this, name);
  
  this.age = age;
}

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

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

alert(instance1.name); // "Nicholas"
alert(instance1.age); // 29

借用構造函數繼承的核心就在於SuperType.call(this, name),「借調」了SuperType構造函數,這樣,SubType的每一個實例都會將SuperType中的屬性複製一份prototype

缺陷:
(1)只能繼承父類的實例屬性和方法,不能繼承原型屬性/方法
(2)沒法實現複用,每一個子類都有父類實例函數的副本,影響性能

3. 組合繼承

核心:結合原型鏈繼承和構造函數繼承經過調用父類構造,繼承父類的屬性並保留傳參的優勢,而後經過將父類實例做爲子類原型,實現函數複用

其背後的思路是使用原型鏈實現對原型屬性和方法的繼承,而經過借用構造函數來實現對實例屬性的繼承,這樣,既經過在原型上定義方法實現了函數複用,又能保證每一個實例都有它本身的屬性

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.constructor = SubType; 
SubType.prototype.sayAge = function(){
    alert(this.age);
};

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

var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27

圖解:

clipboard.png

缺陷:
父類中的實例屬性和方法既存在於子類的實例中,又存在於子類的原型中,不過僅是內存佔用,所以,在使用子類建立實例對象時,其原型中會存在兩份相同的屬性/方法

這個方法是javascript中最經常使用的繼承模式

4. 原型式繼承

核心:直接將某個對象直接賦值給構造函數的原型

function object(obj){
  function F(){}
  F.prototype = obj;
  return new F();
}

object()對傳入其中的對象執行了一次淺複製,將F的原型直接指向傳入的對象

var person = {
  name: "Nicholas",
  friends: ["Shelby", "Court", "Van"]
};

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"

缺點:
(1)原型鏈繼承多個實例的引用類型屬性指向相同,存在篡改的可能
(2)沒法傳遞參數

另外,ES5中存在Object.create()的方法,可以代替上面的object方法

5. 寄生式繼承

核心:在原型式繼承的基礎上,加強對象,返回構造函數

function createAnother(original){ 
  varclone=object(original); // 過調用函數建立一個新對象
  clone.sayHi = function(){ // 以某種方式加強這個對象
    alert("hi");
  };
  return clone; // 返回對象
}

函數的主要做用是爲構造函數新增屬性和方法,以加強函數

var person = {
  name: "Nicholas",
  friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"

缺點:
(1)原型鏈繼承多個實例的引用類型屬性指向相同,存在篡改的可能
(2)沒法傳遞參數

6. 寄生組合式繼承

核心:結合借用構造函數傳遞參數和寄生模式實現繼承

function inheritPrototype(subType, superType){
  var prototype = Object.create(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);
}

var instance1 = new SubType("xyc", 23);
var instance2 = new SubType("lxy", 23);

instance1.colors.push("2"); // ["red", "blue", "green", "2"]
instance1.colors.push("3"); // ["red", "blue", "green", "3"]

圖解:

clipboard.png

寄生組合繼承集合了前面幾種繼承優勢,幾乎避免了上面繼承方式的全部缺陷,是執行效率最高也是應用面最廣的,就是實現的過程相對繁瑣

參考:
JS繼承的實現方式
面向對象的程序設計之繼承
MDN——Object.create()

相關文章
相關標籤/搜索