Javascript繼承知多少?
1寫在前面
我們知道面向?qū)ο蟮木幊陶Z言中都會有繼承,而且在各種語言中充當(dāng)著至關(guān)重要的角色,但是繼承是什么?又有多少種繼承的方式?常見的又有哪些呢?對于js繼承有更深層次的理解,能夠在開發(fā)中應(yīng)對自如。
所謂繼承,繼承是面向?qū)ο蟮?,使用這種方式能夠更好的對代碼的復(fù)用,能夠縮短開發(fā)周期、提升開發(fā)效率。
那么我們帶著兩個問題閱讀文章,在文章解決這些疑惑:
- JS繼承到底有多少種繼承方式?
 - ES6中的extends關(guān)鍵字是使用哪種繼承方式實現(xiàn)的?
 
2繼承的概念
我們知道一個人繼承祖業(yè),可以將父輩所有的物質(zhì)基礎(chǔ)繼承過來,但是自己作為主體又有自己的其它能力和特性。同樣的汽車作為一個大類,生產(chǎn)轎車、跑車、面包車等,都具有汽車四個輪子加發(fā)動機的特性,但各自具有自己獨特的特性,比如五菱宏光可以在秋名山飆車成為車神。
因此,繼承可以使得子類具有父類的各種方法和屬性。
常見的繼承方式有:
- 原型鏈繼承
 - 構(gòu)造函數(shù)繼承
 - 組合繼承
 - 原型鏈繼承
 - 寄生式繼承
 - 寄生組合式繼承
 
3繼承
3.1 原型鏈繼承
原型鏈繼承是比較常見的繼承方式之一,其中涉及的構(gòu)造函數(shù)、原型和實例:
- 每個構(gòu)造函數(shù)都有一個原型對象
 - 原型對象又包含一個指向構(gòu)造函數(shù)的指針
 - 實例則包含一個原型對象的指針
 
- function Person(){
 - this.name = "person";
 - this.abilities = ["吃飯","睡覺","打豆豆"];
 - }
 - function Student(){
 - this.study = ["語文","數(shù)學(xué)","英語"];
 - }
 - Student.prototype = new Person();
 - console.log(new Student());
 
我們可以看到Student類已經(jīng)繼承了Person類的所有特性:
但是,我們注意到:當(dāng)使用同一個對象創(chuàng)建實例的時候,內(nèi)存空間是共享的,當(dāng)一個發(fā)生變化的時候,另外一個也會隨之變化。
- function Person(){
 - this.name = "person";
 - this.abilities = ["吃飯","睡覺","打豆豆"];
 - }
 - function Student(){
 - this.study = ["語文","數(shù)學(xué)","英語"];
 - }
 - Student.prototype = new Person();
 - const stu1 = new Student();
 - const stu2 = new Student();
 - stu1.abilities.push("走路");
 - console.log(stu1.abilities,stu2.abilities);
 
我們看到stu1和stu2都是由Student對象進行創(chuàng)建的兩個實例,當(dāng)改變stu1的值,stu2的值也隨之改變了。圖片
3.2 構(gòu)造函數(shù)繼承(借助call)
構(gòu)造函數(shù)繼承可以很好的解決原型鏈繼承的共享內(nèi)存的弊端,但是父類原型對象中存在父類之前自己定義的方法,那么子類將無法繼承這些方法,此時去使用父類方法就會報錯。
構(gòu)造函數(shù)繼承只能繼承父類實例、屬性和方法,不能繼承原型屬性和方法。
- function Person(){
 - this.name = "person";
 - }
 - Person.prototype.getName = function(){
 - return this.name;
 - }
 - function Student(){
 - Person.call(this);
 - this.study = ["語文","數(shù)學(xué)","英語"];
 - }
 - const stu = new Student();
 - console.log(stu);
 - console.log(stu.getName())
 
我們可以看到,打印的stu實例包含了Student對象以及父類Person所有的屬性,但是要使用父類原型的方法就會報錯。getName是父類Person的引用方法,不會共享內(nèi)存。
3.3 組合繼承(原型鏈繼承+構(gòu)造函數(shù)繼承)
- function Person(){
 - this.name = "person";
 - this.abilities = ["吃飯","睡覺","打豆豆"];
 - }
 - //第一次調(diào)用Person()
 - Person.prototype.getName = function(){
 - return this.name;
 - }
 - function Student(){
 - //第二次調(diào)用Person()
 - Person.call(this);
 - this.study = ["語文","數(shù)學(xué)","英語"];
 - }
 - Student.prototype = new Person();
 - //手動掛載構(gòu)造器,指向自己的構(gòu)造函數(shù)
 - Student.prototype.constructor = Student;
 - const stu1 = new Student();
 - const stu2 = new Student();
 - stu1.abilities.push("走路");
 - console.log(stu1.abilities,stu2.abilities);//不會互相影響
 - console.log(stu1.getName());//正常輸出"person"
 - console.log(stu2.getName());//正常輸出"person"
 
運行得到:我們看到在stu1實例的abilities數(shù)組中追加元素,并不會影響到stu2實例的值。圖片我們發(fā)現(xiàn)使用組合式繼承時,可以有效解決原型鏈繼承和構(gòu)造函數(shù)繼承的缺點,但是,調(diào)用兩次Person(),這就造成了性能開銷,那么我們還有沒有優(yōu)化空間呢?
3.4 原型式繼承
我們可以利用es5中的Object.create()方法進行原型式繼承,從而實現(xiàn)對組合式繼承的優(yōu)化。Object.create()接收兩個參數(shù):
用作新對象原型的對象
為新對象定義額外屬性的對象(可選參數(shù))
- const person = {
 - name:"person",
 - abilities:["吃飯","睡覺","打豆豆"],
 - getName(){
 - return this.name;
 - }
 - }
 - const person1 = Object.create(person);
 - person1.name = "human";
 - person1.abilities.push("走路");
 - const person2 = Object.create(person);
 - person2.abilities.push("跑步");
 - console.log(person1.name);
 - console.log(person1.name === person1.getName());
 - console.log(person2.name);
 - console.log(person1.abilities);
 - console.log(person2.abilities);
 
我們可以看到使用Object.create()可以實現(xiàn)普通對象繼承,不僅可以繼承屬性,還能繼承方法。但是也有缺點:包含引用類型的屬性值始終都會共享相應(yīng)的值,這點跟原型鏈繼承一樣。
修改person1.name的值,person2.name的值并未發(fā)生改變,并不是因為person1和person2有獨立的 name 值,而是因為person1.name = 'human',給person1添加了 name 值,并非修改了原型上的 name 值。
3.5 寄生式繼承
寄生式繼承:首先使用原型式繼承可以獲得一份目標(biāo)對象的淺拷貝,然后利用這個淺拷貝的能力再進行增強,添加一些方法。
寄生式繼承相比于原型式繼承,還是在父類基礎(chǔ)上添加了更多方法。
- function clone(original){
 - const clone = Object.create(original);
 - clone.getAbilities = function(){
 - return this.abilities;
 - }
 - return clone;
 - }
 - const person = {
 - name:"person",
 - abilities:["吃飯","睡覺","打豆豆"],
 - getName(){
 - return this.name;
 - }
 - }
 - const person1 = clone(person);
 - console.log(person1.getName());
 - console.log(person1.getAbilities());
 
運行得到:
3.6 寄生組合式繼承
前面分析了五種常見的繼承方法,現(xiàn)在綜合所有方式的優(yōu)缺點,可以進行優(yōu)化改造得到寄生組合式的繼承方式,這也是所有繼承方式中相對最優(yōu)的。
- function clone(parent,child){
 - //這里使用Object.create()可以減少組合繼承中多進行一次構(gòu)造函數(shù)的過程
 - child.prototype = Object.create(parent.prototype);
 - child.prototype.constructor = child;
 - }
 - function Parent(){
 - this.name = "parent";
 - this.abilities = ["吃飯","睡覺","打豆豆"];
 - }
 - Parent.prototype.getName = function(){
 - return this.name;
 - }
 - function Child(){
 - Parent.call(this);
 - this.study = ["語文","數(shù)學(xué)","英語"];
 - }
 - clone(Parent,Child);
 - Child.prototype.getStudy =function(){
 - return this.study;
 - }
 - const child = new Child();
 - console.log(child);
 - console.log(child.getName());
 - console.log(child.getStudy());
 
運行得到:
extends關(guān)鍵字主要用于類聲明或者類表達式中,以創(chuàng)建一個類,該類是另一個類的子類。其中constructor表示構(gòu)造函數(shù),一個類中只能有一個構(gòu)造函數(shù),有多個會報出SyntaxError錯誤,如果沒有顯式指定構(gòu)造方法,則會添加默認的 constructor方法,使用例子如下。
- class Rectangle {
 - // constructor
 - constructor(height, width) {
 - this.height = height;
 - this.width = width;
 - }
 - // Getter
 - get area() {
 - return this.calcArea()
 - }
 - // Method
 - calcArea() {
 - return this.height * this.width;
 - }
 - }
 - const rectangle = new Rectangle(10, 20);
 - console.log(rectangle.area);
 - // 輸出 200
 - ----------------------------華麗的分割線-------------------------------------
 - // 繼承
 - class Square extends Rectangle {
 - constructor(length) {
 - super(length, length);
 - // 如果子類中存在構(gòu)造函數(shù),則需要在使用“this”之前首先調(diào)用 super()。
 - this.name = 'Square';
 - }
 - get area() {
 - return this.height * this.width;
 - }
 - }
 - const square = new Square(10);
 - console.log(square.area);
 - // 輸出 100
 
extends繼承的核心代碼如下,其實現(xiàn)和上述的寄生組合式繼承方式一樣。
- function _inherits(subType, superType) {
 - // 創(chuàng)建對象,創(chuàng)建父類原型的一個副本
 - // 增強對象,彌補因重寫原型而失去的默認的constructor 屬性
 - // 指定對象,將新創(chuàng)建的對象賦值給子類的原型
 - subType.prototype = Object.create(superType && superType.prototype, {
 - constructor: {
 - value: subType,
 - enumerable: false,
 - writable: true,
 - configurable: true
 - }
 - });
 - if (superType) {
 - Object.setPrototypeOf
 - ? Object.setPrototypeOf(subType, superType)
 - : subType.__proto__ = superType;
 - }
 - }
 
4參考文章
《JavaScript常用八種繼承方案》
《深入JavaScript繼承原理》
5寫在最后
繼承的方法很多,每個實現(xiàn)的方法都比較零散,需要對常見的繼承方法進行一個深入系統(tǒng)的分析總結(jié)。圖片




















 
 
 


 
 
 
 