JS實現繼承的幾種方式以及優缺點


JS實現繼承的幾種方式以及優缺點

大多面向對象語言都支持兩種繼承方式:接口繼承和實現繼承 ,而ECMAScript只支持實現繼承,且實現繼承主要是依靠原型鏈來實現。

先定義一個父類

<code>// 定義一個動物類
function Animal (name) {
// 屬性
this.name = name || 'Animal';
this.colors = ["black","red"]; //引用類型的屬性
// 實例方法
this.sleep = function(){
console.log(this.name + '正在睡覺!');
}
}
// 原型方法
Animal.prototype.eat = function(food) {
console.log(this.name + '正在吃:' + food);
};/<code>

原型鏈繼承

基本思想:利用原型對象讓一個引用類型繼承另外一個引用類型的屬性和方法。核心就是將父類的實例作為子類的原型對象。

構造函數,原型,實例之間的關係:每個構造函數都有一個原型對象prototype,原型對象prototype包含一個指向構造函數的指針constructor,而實例都包含一個指向原型對象的內部指針__proto__。

原型鏈實現繼承例子:

<code>function Cat(){ 
}
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';

// Test Code
var cat = new Cat();
cat.name; // "cat"
cat.eat('fish'); // cat正在吃:fish
cat.sleep(); // cat正在睡覺!
cat instanceof Animal; //true
cat instanceof Cat; //true
cat.colors.push("orange");
cat.colors; // ["black","red","orange"]

//引用類型的屬性共享問題
var cat2 = new Cat();
cat2.colors: // ["black","red","orange"]/<code>

優點:

  1. 非常純粹的繼承關係,實例是子類的實例,也是父類的實例
  2. 父類新增原型方法/原型屬性,子類都能訪問到
  3. 簡單,易於實現

缺點:

  1. 要想給子類新增屬性和方法,必須在子類的原型對象上新增,不能放到子類構造函數中
  2. 無法實現多繼承
  3. 來自父類的引用類型屬性被所有子類的實例共享
  4. 創建子類實例時,無法向父類構造函數傳參

借用構造函數

基本思想:在子類構造函數的內部調用父類構造函數,通過使用call()和apply()方法可以在新創建的對象上執行構造函數。核心就是使用父類的構造函數來增強子類實例,等於是複製父類的實例屬性給子類(沒用到原型)。

<code>function Cat(name){
Animal.call(this,name);
}

// Test Code
var cat = new Cat("Kevin");
cat.name; //"Kevin"
cat.sleep(); //Kevin正在睡覺!
cat instanceof Animal; // false
cat instanceof Cat; // true
cat.colors.push("orange");
cat.colors; // ["black","red","orange"]

//不存在引用類型的屬性共享問題
var cat2 = new Cat();
cat2.colors; // ["black", "red"]

//不能繼承父類的原型屬性/方法
console.log(cat.eat); //undefined/<code>

優點:

  1. 解決了原型鏈繼承中,子類的實例共享父類引用類型屬性的問題
  2. 創建子類實例時,可以向父類傳遞參數
  3. 可以實現多繼承(call多個父類對象)

缺點:

  1. 實例並不是父類的實例,只是子類的實例
  2. 只能繼承父類的實例屬性和方法,不能繼承父類的原型屬性/方法
  3. 無法實現函數複用,每個子類都有父類實例函數的副本,影響性能

組合繼承

基本思想:將原型鏈和借用構造函數的技術組合在一塊,從而發揮兩者之長的一種繼承模式。核心就是通過調用父類構造函數,繼承父類的屬性並保留傳參的優點,然後通過將父類實例作為子類原型對象,實現函數複用。

<code>function Cat(name){
Animal.call(this, name); //繼承屬性 第二次調用Animal()
}
//繼承方法
Cat.prototype = new Animal(); //第一次調用Animal()
Cat.prototype.constructor = Cat; //修復構造函數指向

// Test Code

var cat = new Cat("Kevin");
cat.name; //"Kevin"
cat.sleep(); //Kevin正在睡覺!
cat instanceof Animal; // true
cat instanceof Cat; // true
cat.colors.push("orange");
cat.colors; // ["black","red","orange"]
cat.eat('fish'); // Kevin正在吃:fish

//不存在引用類型的屬性共享問題
var cat2 = new Cat();
cat2.colors; // ["black", "red"]/<code>

優點:

  1. 彌補了借用構造函數的缺陷,可以繼承實例屬性/方法,也可以繼承原型屬性/方法
  2. 既是子類的實例,也是父類的實例
  3. 不存在引用屬性共享問題
  4. 可傳參
  5. 函數可複用

缺點:

  1. 調用了兩次父類構造函數,生成了兩份實例(子類實例將子類原型上的那份屏蔽了)

原型式繼承

基本想法:藉助原型可以基於已有的對象創建新對象,同時還不必須因此創建自定義的類型。

原型式繼承的思想可用以下函數來說明:

<code>function object(o) {
function F(){}
F.prototype = o;
return new F();
}/<code>

在object()函數內部,先創建了要一個臨時性的構造函數,然後將傳入的對象作為這個構造函數的原型,最後返回了這個臨時類型的一個新實例。從本質上將,object()對傳入其中的對象執行力一次淺複製。

《JavaScript高級程序設計(第三版)》

<code>var person = {
name:"EvanChen",
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");
person.friends;//"Shelby","Court","Van","Rob","Barbie"/<code>

ECMAScript5通過新增Object.create()方法規範化了原型式繼承,這個方法接收兩個參數:一個用作新對象原型的對象和一個作為新對象定義額外屬性的對象。

《JavaScript高級程序設計(第三版)》

<code>var person = {
name:"EvanChen",
friends:["Shelby","Court","Van"];

};
var anotherPerson = Object.create(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
console.log(person.friends);//"Shelby","Court","Van","Rob","Barbie"/<code>

優點:

  1. 不需要創建構造函數

缺點:

  1. 存在引用類型的屬性共享問題
  2. 只適用一個對象繼承另一個對象

寄生式繼承

基本思想:創建一個僅用於封裝繼承過程的函數,該函數在內部以某種方式來增強對象,最後再像真正是它做了所有工作一樣返回對象。

<code>function object(o) {
function F(){}
F.prototype = o;
return new F();
}
//需要在原型式繼承的基礎上實現
function createAnother(original) {
var clone = object(original);
clone.sayHi = function () {
alert("hi");
};

return clone;
}
var person = {
name:"EvanChen",
friends:["Shelby","Court","Van"];
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi();//"hi"/<code>

優點:

  1. 不需要創建構造函數

缺點:

  1. 存在引用類型的屬性共享問題
  2. 只適用一個對象繼承另一個對象
  3. 無法實現函數複用,影響性能

寄生組合繼承

基本思想:通過借用構造函數來繼承屬性,通過原型鏈的混成形式來繼承方法。

基本模型:

<code>function inheritProperty(subType, superType) {
var prototype = object(superType.prototype);//創建對象,使用原型式繼承的object方法
prototype.constructor = subType;//增強對象
subType.prototype = prototype;//指定對象
}/<code>

例子:

<code>function Cat(name){
Animal.call(this,name);//繼承屬性
}

inheritProperty(Cat, Animal);//繼承方法
// Test Code
var cat = new Cat("Kevin");
cat.name; //"Kevin"
cat.sleep(); //Kevin正在睡覺!
cat instanceof Animal; // true
cat instanceof Cat; //true/<code>

優點:

  1. 只調用了一次父類的構造函數,效率高,解決了組合繼承的缺點,堪稱完美

缺點:

  1. 實現較為複雜

ES6的類繼承

<code>class Parent {
constructor(x,y){
this.x = x;
this.y = y;
}
toString(){

}
}

class Child extends Parent{
constructor(x,y,color){
super(x,y);
this.color = color;
}
toString(){
return super.toString();

}
}/<code>

通過extends關鍵字繼承了Parent類所有的屬性和方法。子類的constructor方法 和 toString方法中都出現了super關鍵字,在這裡表示父類的構造函數,用來新建父類的this對象。子類必須在constructor方法中調用super方法,因為子類的this對象必須通過父類的構造函數完成創建, 並且得到與父類同樣的實例屬性和方法。如果不調用,子類就得不到this對象,就不能進行二次加工。

要注意的一個地方是,在子類的構造函數中,只有調用super才可以使用this關鍵字, 所以this關鍵字要放到super關鍵字之後, 是因為子類實例的構建,基於父類實例,只有super方法能調用父類實例。

優點:

  1. 使用方便,容易理解

缺點:

  1. 不兼容低端瀏覽器




分享到:


相關文章: