Skip to content

JS实现继承的几种方式

Posted on:June 12, 2020
  1. 原型链继承

    核心:将父类的实例作为子类的原型

    //父类型
    function Person(name, age) {
      (this.name = name), (this.age = age), (this.play = [1, 2, 3]);
      this.setName = function () {};
    }
    Person.prototype.setAge = function () {};
    
    // 子类
    function Student(price) {
      this.price = price;
      this.setScore = function () {};
    }
    Student.prototype = new Person(); // 子类型的原型为父类型的一个实例对象
    var s1 = new Student(15000);
    var s2 = new Student(14000);
    console.log(s1, s2);
  1. 构造函数继承

    核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)

    function Person(name, age) {
      (this.name = name), (this.age = age), (this.setName = function () {});
    }
    
    Person.prototype.setAge = function () {};
    
    function Student(name, age, price) {
      Person.call(this, name, age); // 相当于: this.Person(name, age)
      /*this.name = name
      this.age = age*/
      this.price = price;
    }
    
    var s1 = new Student("Tom", 20, 15000);

    优点:

    • 解决了 1 中,子类实例共享父类引用属性的问题
    • 创建子类实例时,可以向父类传递参数
    • 可以实现多继承(call 多个父类对象)

    缺点:

    • 实例并不是父类的实例,只是子类的实例
    • 只能继承父类的实例属性和方法,不能继承原型属性/方法
    • 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

  2. 原型链+借用构造函数的组合继承

    核心: 通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用

    function Cat(name, age) {
      Animal.call(this, name);
      this.age = age;
      this.run = function () {};
    }
    
    Cat.prototype = new Animal();
    Cat.prototype.constructor = Cat; // 注意:这里必须改变子类原型的构造函数
    
    // Test Code
    var cat = new Cat("red", 27);
    console.log(cat.name);
    console.log(cat.sleep());
    console.log(cat instanceof Animal); // true
    console.log(cat instanceof Cat); // true

    优点:

    • 可以继承实例属性/方法,也可以继承原型属性/方法
    • 不存在引用属性共享问题
    • 可传参
    • 函数可复用

    缺点:

    • 调用了两次父类构造函数,生成了两份实例

  1. 组合继承优化 1:

    核心: 这种方式通过父类原型和子类原型指向同一对象,子类可以继承到父类的公有方法当做自己的公有方法,而且不会初始化两次实例方法/属性,避免的组合继承的缺点。

    function Person(name, age) {
      (this.name = name), (this.age = age), (this.setAge = function () {});
    }
    Person.prototype.setAge = function () {
      console.log("111");
    };
    function Student(name, age, price) {
      Person.call(this, name, age);
      this.price = price;
      this.setScore = function () {};
    }
    Student.prototype = Person.prototype;
    Student.prototype.sayHello = function () {};
    var s1 = new Student("Tom", 20, 15000);
    console.log(s1);

    优点:

    • 不会初始化两次实例方法/属性,避免的组合继承的缺点

    缺点:

    • 没办法辨别是实例是子类还是父类创造的,子类和父类的构造函数指向是同一个

  2. 组合继承优化 2

    核心: 借助原型可以基于已有的对象来创建对象,var B = Object.create(A)以 A 对象为原型,生成了 B 对象。B 继承了 A 的所有属性和方法

    function Person(name, age) {
      (this.name = name), (this.age = age);
    }
    Person.prototype.setAge = function () {
      console.log("111");
    };
    function Student(name, age, price) {
      Person.call(this, name, age);
      this.price = price;
      this.setScore = function () {};
    }
    Student.prototype = Object.create(Person.prototype); //核心代码
    Student.prototype.constructor = Student; //核心代码
    var s1 = new Student("Tom", 20, 15000);
    console.log(s1 instanceof Student, s1 instanceof Person); // true true
    console.log(s1.constructor); //Student
    console.log(s1);

    优点:

    • 堪称完美

    缺点:

    • 实现较为复杂

  3. ES6 中的 Class 继承

    核心: ES5 的继承,实质是先创造子类的实例对象 this,然后再将父类的方法添加到 this 上面(Parent.apply(this))。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到 this 上面(所以必须先调用 super 方法),然后再用子类的构造函数修改 this

    class Person {
      //调用类的构造方法
      constructor(name, age) {
        this.name = name;
        this.age = age;
      }
      //定义一般的方法
      showName() {
        console.log("调用父类的方法");
        console.log(this.name, this.age);
      }
    }
    let p1 = new Person("kobe", 39);
    console.log(p1);
    //定义一个子类
    class Student extends Person {
      constructor(name, age, salary) {
        super(name, age); //通过super调用父类的构造方法
        this.salary = salary;
      }
      showName() {
        //在子类自身定义方法
        console.log("调用子类的方法");
        console.log(this.name, this.age, this.salary);
      }
    }
    let s1 = new Student("wade", 38, 1000000000);
    console.log(s1);
    s1.showName();

    优点:

    • 语法简单易懂,操作更方便

    缺点:

    • 并不是所有的浏览器都支持 class 关键字,可以借助 Babel 工具语法降级