JS中繼承關係的應用

前言

面向對象編程很重要的一個方面,就是對象的繼承。A對象通過繼承B對象,就能直接擁有B對象的所有屬性和方法。這對於代碼的複用是非常有用的。傳統上,JavaScript語言的繼承不通過class,需要使用原型機制或者用applaycall方法實現。ES6引入了class語法,則出現了基於class的繼承。

繼承解決了什麼?

爲什麼要使用繼承?繼承解決了什麼問題?這裏就不賣關子了,直接給出答案。繼承是爲了解決構造函數的缺陷,解決在同一個構造函數的多個實例之間,無法共享屬性,從而造成對系統資源的浪費。讓我們通過下面例子簡單來分析一下下。

例子1:

function Cat (name, color) {
  this.name = name;
  this.color = color;
}

var cat1 = new Cat('大毛', '白色');

cat1.name // '大毛'
cat1.color // '白色'

以上代碼中,Cat函數是一個構造函數,函數內部定義了name屬性和color屬性,所有實例對象(cat1)都會生成這兩個屬性。下面我們再改造下例子1。

例子2:

function Cat(name, color) {
  this.name = name;
  this.color = color;
  this.meow = function () {
    console.log('喵喵');
  };
}

var cat1 = new Cat('大毛', '白色');
var cat2 = new Cat('二毛', '黑色');

cat1.meow === cat2.meow
// false

例子2中cat1cat2是同一個構造函數的兩個實例,它們都具有meow方法。由於meow方法是生成在每個實例對象上面,所以兩個實例就生成了兩次。ok,我想大家到這都很清楚明白。但是,問題來了,每新建一個實例,這裏面就會新建一個函數方法,這既沒有必要,又浪費系統資源,因爲所有meow方法都是同樣的行爲,完全可以共享複用。

繼承的應用

存在即合理。上面通過例子大概瞭解繼承出現是爲了幹什麼,現在咱們得知道它是如何幹活的,即繼承要如何實現呢?開始之前看看都要哪些方法可以實現繼承。

  • 原型鏈繼承
  • 構造函數繼承
  • call方法繼承
  • applay方法繼承
  • 組合繼承
  • ES6實現繼承

1、原型鏈繼承

將構造函數的原型設置爲另一個構造函數的實例對象,這樣就可以繼承另一個原型對象的所有屬性和方法,可以繼續往上,最終形成原型鏈。

子類通過prototype將所有在父類中通過prototype追加的屬性和方法都追加到子類,從而實現繼承。爲了讓子類繼承父類的屬性(也包括方法),首先需要定義一個構造函數,然後,將父類的新實例賦值給構造函數原型。具體看下面代碼。

function parent(){
    this.name="garuda";
}
function child(){
    this.sex="man"
}
child.prototype=new parent();//核心:子類繼承父類,通過原型形成鏈條
var test=new child();
console.log(test.name);
console.log(test.sex);
在js中,被繼承的函數稱爲超類型(父類、基類),繼承的函數稱爲子類型(子類、派生類)。

使用原型繼承存在兩個問題:一是面量重寫原型會中斷關係,使用引用類型的原型,二是子類型還無法給超類型傳遞參數。

2、借用構造函數繼承

爲了解決原型中包含引用類型值的問題,開始使用借用構造函數,也叫僞造對象或經典繼承
function parent(){
    this.name="garuda";
}
function child(){
    parent.call(this);//核心:借父類型構造函數增強子類型(傳參)
}
var test =new parent();
console.log(test.name);

存在的問題就是,所有的類型都只能使用構造函數模式(因爲超類型的原型中定義的方法對於子類型不可見),因此方法都在構造函數中定義,函數複用就無從談起了。

3、call方法繼承

call方法是Function類中的方法call方法的第一個參數的值賦值給類(即方法)中出現的thiscall方法的第二個參數開始依次賦值給類(即方法)所接受的參數(參數列表)。

function test(str){
    alert(this.name + " " + str);
  }
var object = new Object();
object.name = "zhangsan";
test.call(object,"langsin");
//此時,第一個參數值object傳遞給了test類(即方法)中出現的this,
// 而第二個參數"langsin"則賦值給了test類(即方法)的str
  function Parent(username){
    this.username = username;
    this.hello = function(){
      alert(this.username);
    }
  }
  function Child(username,password){
    Parent.call(this,username); 
    this.password = password;
    this.world = function(){
      alert(this.password);
    }
  }
  var parent = new Parent("zhangsan");
  var child = new Child("lisi","123456");
  parent.hello();
  child.hello();
  child.world();

4、apply方法繼承

apply方法接受2個參數,第一個參數與call方法的第一個參數一樣,即賦值給類(即方法)中出現的this,第二個參數爲數組類型,這個數組中的每個元素依次賦值給類(即方法)所接受的參數(數組參數)。

function Parent(username){
    this.username = username;
    this.hello = function(){
      alert(this.username);
    }
  }
  function Child(username,password){
    Parent.apply(this,new Array(username));
    this.password = password;
    this.world = function(){
      alert(this.password);
    }
  }
  var parent = new Parent("zhangsan");
  var child = new Child("lisi","123456");
  parent.hello();
  child.hello();
  child.world();

5、組合繼承(原型鏈和構造函數組合)

也叫僞經典繼承,將原型鏈和借用構造函數的技術組合到一塊。使用原型鏈實現對原型屬性和方法的繼承,而通過構造函數來實現對實例屬性的繼承。
function parent(){
    this.name="garuda";
}
function borther(){
    return this.name;
}
function child(){
    parent.call(this)
}
child.prototype=new parent();
var test=new parent();
console.log(test.borther())

實際上是借用了構造函數,以覆蓋的方式,解決了在原型鏈繼承中原型的引用類型屬性共享在所有實例中的問題。

6、ES6實現繼承

ES6class是語法糖,其實質就是函數,而上述用class實現繼承的過程,還是基於原型鏈(和ES5的是不是完全一致)

// ES6 寫法
class Human{
     constructor(name){
         this.name = name
     }
     run(){
         console.log("我叫"+this.name+",我在跑")
         return undefined
     }
 }
 class Man extends Human{ // extends 實現上述繼承過程
     constructor(name){
         super(name) // 調用構造函數:'超類'
         this.gender = '男'
     }
     fight(){
         console.log('糊你熊臉')
     }
 }

總結

JS中的繼承關係是很重要的技術知識,在實際開發中經常會用到,不瞭解的童鞋需要加緊學習理解哦!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章