繼承是 OO 語言中一個最爲津津樂道的概念,許多 OO 語言都支持兩種繼承方式:接口繼承和實現繼承。接口繼承只繼承方法簽名,而實現繼承則繼承實際的方法。因爲函數沒有簽名,在 ECMAScript 中沒法實現接口繼承。ECMAScript 只支持實現繼承並且實現繼承主要是依靠原型鏈來實現的。
關於原型鏈,我以前的文章裏面有介紹,若是有些忘記了,能夠看這篇文章。
下面我將詳細的介紹前端前輩在開發過程當中不斷摸索創造的幾種繼承方式。看完面試的時候千萬不要簡單的回答 call 跟 apply 了。
爲了提及來省事,雖然 js 沒有嚴格意義的類,我仍是以父類和子類來作區分繼承關係。javascript
既然子類想要繼承父類的所有方法,並且咱們知道父類的實例擁有父類全部的方法,那麼接下類就好辦了,我將子類的 prototype 指向父類的實例,子類就擁有了父類的所有方法了html
// 定義父類 function Parent (name, age) { this.name = name; this.age = age; } Parent.prototype.sayName = function () { alert(this.name); } // 定義子類 function Child (sex) { this.sex = sex; } // 實現繼承 var p = new Parent('leizore', 25); Child.prototype = p; var child = new Child('男'); child.sayName(); // leizore
那麼對應的關係圖以下:
前端
這種方式 Child 繼承了 Person 的所有方法,可是也是有缺點的。java
針對上面的繼承方法的缺點1,開發人員使用一種叫作借用構造函數的技術,也就是咱們平時說的 call 跟 apply 繼承。es6
// 定義父類 function Parent (name, age) { this.name = name; this.age = age; } Parent.prototype.sayName = function () { alert(this.name); } // 定義子類 function Child (name, age, sex) { // 繼承,同時傳遞了參數 Parent.call(this, name, age) this.sex = sex; }
這裏簡單講一下 call(apply)是如何實現的,其實就是將 call(apply) 前面的函數當即執行一遍,而且執行時將做用域 this 指向 call(apply) 函數的第一個參數,好比這裏的 call 就是將 Parent 實例一遍,將 name 跟 age 當成參數傳過去
這種繼承方式解決了繼承過程當中的傳參問題,可是缺點是並無繼承到父類的原型,爲了解決這個問題,咱們很容易想到將上面兩個方法結合起來不久好了。因而另外一種繼承方式出現了面試
沒錯,就是兩種方式並用,從而發揮二者之長的一種繼承模式,代碼以下編程
// 定義父類 function Parent (name, age) { this.name = name; this.age = age; } Parent.prototype.sayName = function () { alert(this.name); } // 定義子類 function Child (name, age, sex) { // 繼承,同時傳遞了參數 Parent.call(this, name, age) this.sex = sex; } Child.prototype = new Parent('leizore', 25);
嗯,這種方式基本上解決了開發過程當中繼承的痛點,成爲好多人經常使用的繼承模式之一。可是缺點也是有的segmentfault
那麼確定有人會說,既然Child.prototype.constructor 不指向本身,那麼直接讓他指向本身不就行了?數組
Child.prototype.constructor = Child;
答案是不行的。由於 Child.prototype 是 Parent 的實例,這樣操做會將 Parent.prototype.constructor 也指向 Child,顯然也是不合理的。app
爲了解決上面 Child 與 Parent 繼承以後糾纏不清的問題,道格拉斯在2006年提出一種繼承方法,它的想法是藉助原型能夠給予已有的對象建立新對象,同時還沒必要所以建立自定義類型。函數以下
function object (o) { function F() {} F.prototype = o; return new F(); }
這個模式至關與建立一個新的對象,對象繼承了o全部屬性,固然這裏也只是實現了淺拷貝。
嗯,想必你們也想到了,上面這種繼承方式能夠解決 Child 與 Parent 繼承後的糾纏不清的關係。能夠由 object 方法建立一個臨時對象,從而斬斷跟 Parent 的聯繫。就能夠放心的對 Child 原型的constructor 隨便指了,固然了爲了繼承鏈的不紊亂,仍是指向本身比較好
// 定義父類 function Parent (name, age) { this.name = name; this.age = age; } Parent.prototype.sayName = function () { alert(this.name); } // 定義子類 function Child (name, age, sex) { // 繼承,同時傳遞了參數 Parent.call(this, name, age) this.sex = sex; } function object (o) { function F() {} F.prototype = o; return new F(); } var prototype = object(Parent.prototype); prototype.constructor = Child; Child.prototype = prototype; var c = new Child('leizore', 11, 'men'); c.sayName() // leizore c.constructor === Child // true
到此,基本上解決了上面所說的全部缺點。固然了,也是有一點問題的,就是方法四的實現實際上是淺拷貝,若是 Parent.prototype 裏又引用類型好比數組,對象,改變Parent.prototype,Child 也會跟着變,解決方式也很簡單,使用深拷貝就好了,同時又能夠寫不少繼承方式。固然了,按照我上面順下來的思想,也能夠寫出本身的繼承方式
好比下面改變object函數:
// 定義父類 function Parent (name, age) { this.name = name; this.age = age; } Parent.prototype.sayName = function () { alert(this.name); } // 定義子類 function Child (name, age, sex) { // 繼承,同時傳遞了參數 Parent.call(this, name, age) this.sex = sex; } function object (o) { var c = {}; for (var i in o) { c[i] = o[i]; } return c } var prototype = object(Parent.prototype); prototype.constructor = Child; Child.prototype = prototype; var c = new Child('leizore', 11, 'men'); c.sayName() // leizore c.constructor === Child // true
固然了,es6 中,能夠經過extends關鍵字實現繼承,這裏就很少說了