JS繼承的幾種方式


JS作為面向對象的弱類型語言,繼承也是其非常強大的特性之一。

既然要實現繼承,那么我們先定義一個父類:

// 定義一個動物類
function Animal (name) {
  // 屬性
  this.name = name || 'Animal';
  // 實例方法
  this.sleep = function(){
    alert(this.name + '正在睡覺!');
  }
}
// 原型方法
Animal.prototype.eat = function(food) {
  alert(this.name + '正在吃:' + food);
};

1、原型鏈繼承

核心: 將父類的實例作為子類的原型

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

// Test Code
var cat = new Cat();
alert(cat.name);
alert(cat.eat('fish'));
alert(cat.sleep());
alert(cat instanceof Animal); //true 
alert(cat instanceof Cat); //true

特點:

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

缺點:

  1. 要想為子類新增屬性和方法,必須要在new Animal()這樣的語句之后執行,不能放到構造器中
  2. 無法實現多繼承
  3. 來自原型對象的引用屬性是所有實例共享的
  4. 創建子類實例時,無法向父類構造函數傳參

2、借用構造函數繼承

核心:使用父類的構造函數來增強子類實例,等於是復制父類的實例屬性給子類(沒用到原型)

function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}

// Test Code
var cat = new Cat();
alert(cat.name);
alert(cat.sleep());
alert(cat instanceof Animal); // false
alert(cat instanceof Cat); // true

特點:

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

缺點:

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

3、組合繼承

核心:通過調用父類構造函數,繼承父類的屬性並保留傳參的優點,然后通過將父類實例作為子類原型,繼承父類屬性和方法,實現函數復用

function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}
Cat.prototype = new Animal();

// Test Code
var cat = new Cat();
alert(cat.name);
alert(cat.sleep());
alert(cat instanceof Animal); // true
alert(cat instanceof Cat); // true

特點:

  1. 彌補了方式2的缺陷,可以繼承實例屬性/方法,也可以繼承原型屬性/方法
  2. 既是子類的實例,也是父類的實例
  3. 不存在引用屬性共享問題
  4. 可傳參
  5. 函數可復用
  6. 可以實現多繼承(同上)

缺點:

  1. 調用了兩次父類構造函數,生成了兩份實例(子類實例將子類原型上的那份屏蔽了)(僅僅多消耗了一點內存)

4原型式繼承

核心:繼承已有的對象而不是函數

//對傳入的對象執行一次淺復制
function object(o){
      function F(){}
      F.prototype = o;
      return new F();
}
//例子
var Animal = {
      name:"cat",
      friend:["aaron","cassic"]
}
var cat=object(Animal);
cat.name="sss";
cat.friend.push("aseaff");
alert(cat.name);
alert(cat.friend);

特點:

  1. 沒有必要創建構造函數,只是想讓一個對象與另一個對象保持類似

缺點:

  1. 與原型模式一樣,包含引用類型的屬性值會共享

5寄生式繼承

核心 與原型式繼承緊密相關,只是多了方法

//對傳入的對象執行一次淺復制
function object(o){
      function F(){}
      F.prototype = o;
      return new F();
}
//增強對象
function createobject(o){
     var clone=object(o);
     clone.sayname=function(){
        alert("hi");
};
    return clone;
}
//例子
var Animal = {
      name:"cat",
      friend:["aaron","cassic"]
}
var cat=createobject(Animal);
cat.sayname();

特點:

  1. 沒有必要創建構造函數,只是想讓一個對象與另一個對象保持類似
  2. 與原型式繼承相比,有了自己的方法

缺點:

  1. 與原型式繼承一樣,包含引用類型的屬性值會共享

6、寄生組合繼承

核心:通過寄生方式,創建空函數,砍掉父類的實例屬性,這樣,在調用兩次父類的構造的時候,就不會初始化兩次實例方法/屬性,避免的組合繼承的缺點

function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}
function aaron(o,y){
var Super = function(){};
Super.prototype=y.prototype;
o.prototype=new Super();
o.prototype.constructor=o;
} aaron(Cat,Animal); // Test Code var cat = new Cat(); alert(cat.name); cat.sleep(); alert(cat instanceof Animal); // true alert(cat instanceof Cat); //true

特點:

  1. 堪稱完美

缺點:

  1. 實現較為復雜
  2. 不能實現多繼承(不完美,同上)

下面介紹多繼承

7 拷貝繼承

function Cat(name){
  var animal = new Animal();
  for(var p in animal){
    Cat.prototype[p] = animal[p];
  }
  Cat.prototype.name = name || 'Tom';
}

// Test Code
var cat = new Cat();
alert(cat.name);
cat.sleep();
alert(cat instanceof Animal); // false
alert(cat instanceof Cat); // true

特點:

支持多繼承

缺點:

效率較低,內存占用高(因為要拷貝父類的屬性)(使用 for in 能遍歷原型方法)

8 組合使用構造函數和拷貝實現多繼承

function Parent1(name,age){
    this.name = name;
    this.age = age;
    this.height=180;
}
Parent1.prototype.say = function(){
    alert('hi');
}
function Parent2(name,age,weight){
    this.name = name;
    this.age = age;
    this.weight = weight;
    this.height = 170;
    this.skin='yellow';
}
Parent2.prototype.walk = function(){
    alert('walk');
}
 
function Child(name,age,weight){
    Parent1.call(this,name,age);
    Parent2.call(this,name,age,weight);
}
 
for(var i in Parent1.prototype){Child.prototype[i] = Parent1.prototype[i]}
for(var i in Parent2.prototype){Child.prototype[i] = Parent2.prototype[i]}
 
var c1 = new Child('xiaoming',10,8);
console.log(c1); //Child { name="xiaoming", age=10, height=170, 更多...}
console.log(c1.constructor);//Child(name,age,weight)

特點:javascript的多繼承其實就是前面介紹的js循環拷貝繼承的多次使用

總結:javascript是可以利用call方法和prototype屬性來實現多繼承的。繼承方法與單繼承相似,只是將需要繼承的多個父類依次實現,另外對於屬性或共有方法重命的時候,以最后繼承的屬性和方法為主。因為會覆蓋前面的繼承。

最好的兩個繼承是寄生組合繼承和循環拷貝繼承,但是寄生組合繼承不能完美實現多繼承,會漏掉父類原型方法,只有循環拷貝繼承可以完美實現多繼承。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM