Javascript如何实现继承

80 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第14天,点击查看活动详情

一。Javascript如何实现继承?

是什么:继承(inheritance)是面向对象软件技术当中的一个概念。

如果一个类别B“继承自”另一个类别A,就把这个B称为“A的子类”,而把A称为“B的父类”也可以称“A是B的超类”

继承的优点:

继承可以使得子类具有父类别的各种属性和方法,而不需要再次编写相同的代码,也就是代码复用。

举例:

// 汽车
class Car{
    constructor(color,speed){
        this.color = color
        this.speed = speed
    }
}
// 货车
class Truck extends Car{
    constructor(color,speed){
        super(color,speed)
        this.Container = true // 货箱
    }
}
// 货车是汽车,用于汽车的颜色和速度属性,同时货车有一个自己的属性是货箱。

实现方式:

  • 原型链继承

    function Parent() {
        this.name = 'parent1';
        this.play = [1, 2, 3]
    }
    function Child() {
        this.type = 'child2';
    }
    Child.prototype = new Parent()
    var s1 = new Child2();
    var s2 = new Child2();
    s1.play.push(4);
    // s2的play属性变量化了,这是因为两个实例使用的是同一个原型对象,内存空间是共享的
    console.log(s2.play)
    
  • 构造函数继承(借助 call)

    function Parent(){
      this.name = 'parent';
    }
    Parent.prototype.getName = function () {
        return this.name;
    }
    ​
    function Child(){
      // 执行Parent方法,将当前方法的this传进去
      // 注意:call只是改变了Parent方法,内部this的指向
      Parent.call(this);
      this.type = 'child'
    }
    let child = new Child();
    console.log(child.name)   // 可以
    console.log(child.getName())  // 报错,因为获取不到Parent原型上定义的成员
    
  • 组合继承

    function Parent() {
      this.name = 'parent';
      this.play = [1, 2, 3];
    }
    Parent.prototype.getName = function () {
      return this.name;
    }
    function Child() {
      // 获取到Parent本身的成员
      Parent.call(this);
      this.type = 'child'
    }
    ​
    // 获取到Parent原型上定义的成员
    Child.prototype = new Parent()
    // 校正构造函数指向
    Child.prototype.constructor = Child;
    ​
    let c1 = new Child()
    let c2 = new Child()
    c1.play.push(4)
    console.log(c2.play);
    console.log(c2.getName());
    
  • 原型式继承

    这里主要借助Object.create方法实现普通对象的继承

    let obj1 = {
      name:'张三',
      age:20,
      arr:[1,2,3]
    }
    // 将obj2的原型对象指向obj1
    let obj2 = Object.create(obj1)
    let obj3 = Object.create(obj1)
    obj2.arr.push(4)
    console.log(obj3.arr);
    // 因为Object.create方法实现的是浅拷贝,多个实例的引用类型属性指向相同的内存,存在篡改的可能
    
  • 寄生式继承

    寄生式继承在上面继承基础上进行优化,利用这个浅拷贝的能力再进行增强,添加一些方法

    let obj1 = {
      name:'张三',
      age:20,
      arr:[1,2,3]
    }
    // 封装一个方法,实现继承
    function clone(original) {
        let clone = Object.create(original);
        clone.getName = function() {
            return this.name;
        };
        return clone;
    }
    let obj2 = clone(obj1)
    console.log(obj2.getName());
    
  • 寄生组合式继承

    寄生组合式继承,借助解决普通对象的继承问题的Object.create 方法,在前面几种继承方式的优缺点基础上进行改造,这也是所有继承方式里面相对最优的继承方式

    function clone (parent, child) {
        // 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程
        child.prototype = Object.create(parent.prototype);
        // 校正构造函数指向
        child.prototype.constructor = child;
    }
    function Parent() {
        this.name = 'parent';
        this.play = [1, 2, 3];
    }
    Parent.prototype.getName = function () {
        return this.name;
    }
    function Child() {
        // 改变Parent方法中this的指向,目的是获取Parent方法中的成员
        Parent.call(this);
        this.friends = 'child5';
    }
    // 将Child的原型对象指向Parent的原型对象
    clone(Parent, Child);
    // 在Child原型对象上添加一个成员
    Child.prototype.getFriends = function () {
        return this.friends;
    }
    // 不会修改Parent的原型对象
    console.log(Parent.prototype);
    
  • ES6 中extends关键字直接实现 JavaScript的继承

    class Person {
      constructor(name) {
        this.name = name
      }
      // 原型方法
      // 即 Person.prototype.getName = function() { }
      // 下面可以简写为 getName() {...}
      getName = function () {
        console.log('Person:', this.name)
      }
    }
    class Gamer extends Person {
      constructor(name, age) {
        // 子类中存在构造函数,则需要在使用“this”之前首先调用 super()。
        super(name)
        this.age = age
      }
    }
    const asuna = new Gamer('Asuna', 20)
    asuna.getName() // 成功访问到父类的方法