JavaScript 深入继承

48 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第27天,点击查看活动详情

首先来说继承的概念,继承源于构造函数,基于构造函数实现不同的继承方式,继承方式 有很多种,各有优缺点,我们在实际业务中可能很少自己去写这种继承,但是并不代表可以不了解,面试的时候也会被经常问到这个,所以接下来咱们就看看常见的这继承都有哪几种~

原型继承

  • 核心: 让子类的原型指向父类的实例
  • 代码: 子类.prototype = new 父类
  • 优点: 父类构造函数体内的属性和原型上的方法都可以实现继承
  • 缺点:
    1. 继承下来的属性不在自己身上,在自己的原型上
    2. 一个构造函数的实例,需要再两个地方传递参数
    3. 所有子类的实例继承于父类的属性都一样
    4. 子类和父类方法的地址一样,没有分离
function Dad(m){
}
function Box(a){
    this.a=a;
    console.log(a);
}
Dad.prototype.play=function(){
    console.log("play");
    console.log(this.a,"---play");
}
Box.prototype = new Dad()

借用继承(call或apply继承)

  • 核心: 把父类构造函数当作普通函数使用,改变其this指向,指向子类的实例对象
  • 代码: 在子类构造函数体内书写 父类.call(this,参数,参数...)
  • 优点:
    1. 子类所有继承下来的属性都在自己身上
    2. 子类所有参数都在一个地方传递
    3. 子类所有实例给继承下来的属性赋值 互不影响
  • 缺点: 父类的原型上的方法没有继承
function Box(a){
    this.a=a;
    console.log(a);
}
function Ball(a){
    Box.apply(this,arguments);
}

组合继承

  • 核心: 把 原型继承 和 借用继承 一起使用
  • 优点:
    1. 属性和方法都能继承下来
    2. 属性在自己身上,每一个子类实例都可以自己操作
  • 缺点: 属性在子类的原型上还有一套,没有使用价值,但是占位

ES6 类继承

  • 核心: 就是组合继承的升级版,子类原型上继承下来那套属性做了处理
  • 语法:
    1. class 子类 extends 父类 { }
      • 继承父类原型上的方法
    2. 在子类 constructor 内 书写super(实参)
      • 继承父类的属性
  • 注意: extends 和 super 必须都写, 在 constructor 内 super 必须写在最前面
class Box1{
    constructor(a){
        // this.a=a;
        // console.log(a);
    }
    play(){
        console.log(this.a,"---play");
    }
}

class Ball1 extends Box1{
    constructor(a){
        super(a)
    }
}
var a=new Box1();
var b=new Ball1();
console.log(a)
console.log(b);

寄生式组合继承

Function.prototype.extends=function(superClass){
  // this  继承的子类
  function F(){}
  // 这里来使用一个函数,来继承父类的原型,避免直接使用 父类实例,使得父类在 new 的时候,去执行一次父类内部的代码,而后面还需要使用 apply 借用继承去执行一次父类,再去继承父类的属性和方法, 这样就避免了 执行两次父类,继承两次父类的属性和方法.
  F.prototype=superClass.prototype;
  //通过if判断函数原型的构造器是否是自己,不是自己,重新设置会自己,来继承超类
  if(superClass.prototype.constructor!==superClass){
    Object.defineProperty(superClass.prototype,"constructor",{
      value:superClass
    })
  }
  // 先把子类的原型存一下,以继承了父类的原型以后,让子类原来的原型恢复
  var proto=this.prototype;
  // 原型继承 继承父类原型上的属性和方法
  this.prototype=new F();
  // 获取子类原来的原型中所有的key,通过遍历key,得到原型中对应的这个key的描述对象,将子类原有的原型中的属性恢复
  var names=Reflect.ownKeys(proto);
  for(var i=0;i<names.length;i++){
    var desc=Object.getOwnPropertyDescriptor(proto,names[i]);
    Object.defineProperty(this.prototype,names[i],desc);
  }
  // 借用继承 ---> 给实例化的原型上添加一个 super 的方法,子类当中直接使用实例对象上的 super方法执行, 相当于这里执行, arguments就是子类中构造函数的 arguments,通过参数传入这里,this就是实例化对象,执行父类的函数体内的代码.改变this指向,传入参数 ---> 继承父类的属性和方法
  this.prototype.super=function(arguments){
    superClass.apply(this,arguments);
  }
  //向子类的原型上添加一个属性,这个属性继承了父类的原型,使得子类能够通过改变this指向的方式从写父类原型上的方法
  this.prototype.supers=superClass.prototype;
}
// Box 是父类
Ball.extends(Box);
        
function Ball(a){
  this.super(arguments);
}
Ball.prototype.run=function(){
  console.log("run")
}
Ball.prototype.play=function(){
  //  需要在函数里使用 this, 就需要直接找到父类原型上的方法,改变this指向传参
  this.supers.play.apply(this,arguments)
  console.log("aaa")
}

var b=new Ball(5);
Ball.__proto__.num = 10
b.play();

后记

其实除了 ES6extends继承, 其他的继承方法都是我们或者社区自己创造的方法,也不是官方的命名,我们大致知晓他们的种类和实践方式就可以了,实际运用极少~