JavaScript之對象繼承

JavaScript對象繼承的方法有不少,這裏總結一下幾種比較經常使用的方法。
如今有一個"動物"對象的構造函數。javascript

function Animal(){
    this.species = "動物";
}
Animal.prototype.voice = function(){
    console.log('voice');
}

還有一個"貓"對象的構造函數。html

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

怎樣才能使"貓"繼承"動物"呢?前端

1、原型鏈和構造函數繼承

使用call/apply和Object.create()

第一種方法使用call或apply方法,改變了 this 的指向而實現繼承,將父對象的構造函數綁定在子對象上,即在子對象構造函數中加一行:java

function Cat(name, color){
    Animal.apply(this, arguments); 
    this.name = name;
    this.color = color;
}
var cat1 = new Cat("大毛","黃色");
alert(cat1.species); // 動物

到目前爲止一切看起來都還行,可是咱們很快會發現咱們遇到問題了,當咱們調用cat1.voice()的時候報錯了,顯然Cat並無繼承Animal的原型對象裏的方法。es6

咱們已經定義了一個新的構造器,這個構造器默認有一個空的原型屬性。咱們並無讓Cat從Animal的原型對象裏繼承方法。編程

Cat.prototype = Object.create(Animal.prototype);

這裏咱們的老朋友create()又來幫忙了——在這個例子裏咱們用這個函數來建立一個和Animal.prototype同樣的新的原型屬性值(這個屬性指向一個包括屬性和方法的對象),而後將其做爲Cat.prototype的屬性值。這意味着Cat.prototype如今會繼承Animal.prototype的全部屬性和方法。segmentfault

接下來,在咱們動工以前,還須要完成一件事 — 如今Cat()的prototype的constructor屬性指向的是Aniaml(),這是由咱們生成Animal()的方式決定的。(這篇 Stack Overflow post 文章會告訴您詳細的原理)數組

Cat.prototype.constructor = Cat;

此時再輸入Cat.prototype.constructor就會獲得Cat()。閉包

順帶一提,這是很重要的一點,編程時務必要遵照。下文都遵循這一點,即若是替換了prototype對象,那麼,下一步必然是爲新的prototype對象加上constructor屬性,並將這個屬性指回原來的構造函數。app

o.prototype = {};
    o.prototype.constructor = o;

不使用call或apply方法

第二種方法更常見,可是不使用apply和call,而是使用prototype屬性。

若是"貓"的prototype對象,指向一個Animal的實例,那麼全部"貓"的實例,就能繼承Animal了。

var Cat = function(){};
    Cat.prototype = new Animal();
    Cat.prototype.constructor = Cat;

    var cat1 = new Cat("大毛","黃色");
    console.log(cat1.species); // => 動物

代碼的第一行,咱們將Cat的prototype對象指向一個Animal的實例。
它至關於徹底刪除了prototype 對象原先的值,而後賦予一個新值。可是此時Cat.prototype.constructor依然指向Animal的構造函數,因此須要將其指回Cat。

原型式繼承(prototypal inheritance)(推薦)

利用一個空對象做爲中介。

var F = function(){};
F.prototype = Animal.prototype;
Cat.prototype = new F();
Cat.prototype.constructor = Cat;

F是空對象,因此幾乎不佔內存。這時,修改Cat的prototype對象,就不會影響到Animal的prototype對象。

alert(Animal.prototype.constructor); // Animal

咱們將上面的方法,封裝成一個函數,便於使用。

function extend(C, P) {
    var F = function(){};
    F.prototype = P.prototype;
    C.prototype = new F();
    C.prototype.constructor = Child;
    C.super = P.prototype;
}

使用的時候,方法以下

  extend(Cat,Animal);
  var cat1 = new Cat("大毛","黃色");
  alert(cat1.species); // 動物

這個extend函數,就是YUI庫如何實現繼承的方法。

另外,說明一點,函數體最後一行

  Child.super = Parent.prototype;

意思是爲子對象設一個super屬性,這個屬性直接指向父對象的prototype屬性。這等於在子對象上打開一條通道,能夠直接調用父對象的方法。這一行放在這裏,只是爲了實現繼承的完備性,純屬備用性質。

"非構造函數"的繼承

什麼是"非構造函數"的繼承?

好比,如今有一個對象,叫作"中國人"。

  var Chinese = {
    nation:’中國'
  };

還有一個對象,叫作"醫生"。

  var Doctor ={
    career:’醫生’
  }

請問怎樣才能讓"醫生"去繼承"中國人",也就是說,我怎樣才能生成一個"中國醫生"的對象?

這裏要注意,這兩個對象都是普通對象,不是構造函數,沒法使用構造函數方法實現"繼承"。

深拷貝繼承(推薦)

提到拷貝繼承,不得不提到淺拷貝,淺拷貝只能拷貝一層深度,可是不少時候咱們碰到的對象結構不止一層,因此淺拷貝並不適合咱們實際開發中的使用,這裏也就很少作說明了。

所謂"深拷貝",就是可以實現真正意義上的數組和對象的拷貝。它的實現並不難,只要遞歸調用"淺拷貝"就好了。

  function deepCopy(p, c) {
    var c = c || {};
    for (var i in p) {
      if (typeof p[i] === 'object') {
        c[i] = (p[i].constructor === Array) ? [] : {};
        deepCopy(p[i], c[i]);
      } else {
         c[i] = p[i];
      }
    }
    return c;
  }

使用的時候這樣寫:

  var Doctor = deepCopy(Chinese);

如今,給父對象加一個屬性,值爲數組。而後,在子對象上修改這個屬性:

  Chinese.birthPlaces = ['北京','上海','香港'];
  Doctor.birthPlaces.push('廈門');

這時,父對象就不會受到影響了。

  alert(Doctor.birthPlaces); //北京, 上海, 香港, 廈門
  alert(Chinese.birthPlaces); //北京, 上海, 香港

目前,jQuery庫使用的就是這種繼承方法。

extends 關鍵字實現繼承

這個是 ES6 的語法糖,下面看下es6實現繼承的方法

class Parent {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

class Children extends Parent {
  constructor(name, age, job) {
    this.job = job; // 這裏會報錯
    super(name, age);
    this.job = job; // 正確
  }
}

上面代碼中,子類的constructor方法沒有調用super以前,就使用this關鍵字,結果報錯,而放在super方法以後就是正確的。子類Children的構造函數之中的super(),表明調用父類Parent的構造函數。這是必須的,不然 JavaScript 引擎會報錯。

注意,super雖然表明了父類Parent的構造函數,可是返回的是子類Children的實例,即super內部的this指的是Children,所以super()在這裏至關於Parent.prototype.constructor.call(this)。


參考:

  1. http://www.ruanyifeng.com/blo...
  2. https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Objects/Inheritance


推薦閱讀:
【專題:JavaScript進階之路】
JavaScript之深刻理解閉包
ES6 尾調用和尾遞歸
Git經常使用命令小結
JavaScript之call()理解
JavaScript之對象屬性


我是Cloudy,年輕的前端攻城獅一枚,愛專研,愛技術,愛分享。
我的筆記,整理不易,感謝閱讀、點贊和收藏。
文章有任何問題歡迎你們指出,也歡迎你們一塊兒交流前端各類問題!
相關文章
相關標籤/搜索