JavaScript 是一門動態語言,動態意味着高靈活性,而這尤爲能夠體如今繼承上面。JavaScript 中的繼承有不少種實現方式,能夠分紅下面四類:javascript
前三種有一個共同點,就是沒有「類」的概念,它們在適當的場景下很是有用,不過也由於沒有類,缺失了不少經典面向對象繼承的要素。例如父子對象之間沒有嚴格的傳承關係,即不必定是 is-a 的關係,這決定了沒法將它們直接應用在面向對象分析與設計方面,能夠說它們並非真正的繼承,而是介於繼承和組合之間的代碼複用方案。java
而第四種,類式繼承,不管是使用構造函數仍是 ES6 加入的 class,都能表達明確的繼承關係,在須要對繼承重度使用的場景下,應該使用類式繼承。接下來,本文討論的都是類式繼承。git
有一點須要牢記:繼承是一種強耦合,應該謹慎使用。es6
理解 JavaScript 裏面的類繼承實現方式,我認爲最好的方法是——找一門面向對象機制更爲完善的語言,去理解其中的繼承。實際上,JavaScript 中之前就有的 new 和 ES6 加入的 class,都是參考自 Java 語言。github
不過,這樣的對照學習是有前提條件的,即首先掌握 JavaScript 中的原型、原型鏈和做用域,不然很容易誤解 JavaScript 本質的執行機制。若是已經理解了這些前置知識,就能夠探索一下 JavaScript 中的繼承了。markdown
實現要點:數據結構
function Person(name) {
this.name = name;
}
Person.prototype.printName = function() {
console.log(this.name);
};
function Bob() {
Person.call(this, "Bob");
this.hobby = "Histroy";
}
function inheritProto(Parent, Child) {
var Fn = function() {};
Fn.prototype = Parent.prototype;
Child.prototype = new Fn();
Child.prototype.constructor = Child;
}
inheritProto(Person, Bob);
Bob.prototype.printHobby = function() {
console.log(this.hobby);
};
console.dir(new Bob());
複製代碼
dir 輸出:app
Bob
|-- hobby:"Histroy"
|-- name:"Bob"
|-- __proto__:Person
|-- printHobby:ƒ ()
|-- constructor:ƒ Bob()
|-- __proto__:
|-- printName:ƒ ()
|-- constructor:ƒ Person(name)
|-- __proto__:Object
複製代碼
實現要點:函數
function Person(name) {
this.name = name;
}
Person.prototype.printName = function() {
console.log(this.name);
};
function Bob() {
Person.call(this, "Bob");
this.hobby = "Histroy";
}
Bob.prototype = Object.create(Person.prototype, {
constructor: {
value: Bob,
enumerable: false,
configurable: true,
writable: true
}
});
Bob.prototype.printHobby = function() {
console.log(this.hobby);
};
console.dir(new Bob());
複製代碼
dir 輸出:學習
Bob
|-- hobby:"Histroy"
|-- name:"Bob"
|-- __proto__:Person
|-- printHobby:ƒ ()
|-- constructor:ƒ Bob()
|-- __proto__:
|-- printName:ƒ ()
|-- constructor:ƒ Person(name)
|-- __proto__:Object
複製代碼
實現要點:
class Person {
constructor(name) {
this.name = name;
}
printName() {
console.log(this.name);
}
}
class Bob extends Person {
constructor() {
super("Bob");
this.hobby = "Histroy";
}
printHobby() {
console.log(this.hobby);
}
}
console.dir(new Bob());
複製代碼
dir 輸出:
Bob
|-- hobby:"Histroy"
|-- name:"Bob"
|-- __proto__:Person
|-- constructor:class Bob
|-- printHobby:ƒ printHobby()
|-- __proto__:
|-- constructor:class Person
|-- printName:ƒ printName()
|-- __proto__:Object
複製代碼
編寫代碼時,ES6 class 帶來的最明顯的兩個便利是:
實際上,ES6 圍繞 class 增長了不少新功能,好比繼承這件事情上,與以前不一樣的是:用 class 實現的繼承,既包括類實例的繼承關係,也包括類自己的繼承關係。這裏的類實際上是特殊的 JavaScript 函數,而在 JavaScript 中,函數是對象的子類型,即函數對象,因此也可以體現出原型繼承。
例如,用前面的代碼來講明就是:
// 類實例的繼承關係
Bob.prototype.__proto__ === Person.prototype // true
// 類自己的繼承關係
Bob.__proto__ === Person // true
複製代碼
再來看 ES6 中的 super,子類的方法想借助父類的方法完成一部分工做時,super 就能夠派上用場了,這是比繼承更爲細粒度的代碼複用,不過耦合性也也變得更強了。實際上 super 也有不少功能,既能夠看成函數使用,也能夠看成對象使用。將 class 和 super 結合起來看,就能夠領會一下 JavaScript 與 Java 在繼承上的異同了。
與 Java 相同或很是相似的是:
與 Java 不一樣的是:
比較後可見,真的是和 Java 很是相似。
結合前面的內容,能夠發現從 ES3 到 ES6,JavaScript 中的面向對象部分一直是在向 Java 靠攏的。尤爲增長了 class 和 extends 關鍵字以後,靠攏了一大步。但這些並無改變 JavaScript 是基於原型這一實質。Java 中的類就像對象的設計圖,每次調用 new 建立一個新的對象,就產生一個獨立的對象佔用獨立的內存空間;而在 JavaScript,繼承所作工做其實是在構造原型鏈,全部子類的實例共享的是同一個原型。因此 JavaScript 中調用父類的方法其實是在不一樣的對象上調用同一個方法,即「方法借用」,這種行爲其實是「委託(delegation)」調用。