JavaScript對象繼承的方法有不少,這裏總結一下幾種比較經常使用的方法。
如今有一個"動物"對象的構造函數。javascript
function Animal(){ this.species = "動物"; } Animal.prototype.voice = function(){ console.log('voice'); }
還有一個"貓"對象的構造函數。html
function Cat(name, color){ this.name = name; this.color = color; }
怎樣才能使"貓"繼承"動物"呢?前端
第一種方法使用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;
第二種方法更常見,可是不使用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。
利用一個空對象做爲中介。
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庫使用的就是這種繼承方法。
這個是 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)。
參考:
推薦閱讀:
【專題:JavaScript進階之路】
JavaScript之深刻理解閉包
ES6 尾調用和尾遞歸
Git經常使用命令小結
JavaScript之call()理解
JavaScript之對象屬性
我是Cloudy,年輕的前端攻城獅一枚,愛專研,愛技術,愛分享。
我的筆記,整理不易,感謝閱讀、點贊和收藏。
文章有任何問題歡迎你們指出,也歡迎你們一塊兒交流前端各類問題!