这篇文章称为笔记更为适合一些,内容来源于 《JavaScript高级程序设计 (第三版)》第六章 6.3 继承

JavaScript的几种继承形式

  1. 原型链继承
  2. 借助构造函数继承(经典继承)
  3. 组合继承:原型链 + 借用构造函数(最罕用)
  4. 原型式继承 (Object.create)
  5. 寄生式继承
  6. 寄生组合式继承(最现实)

    1. ES6中的继承

1. 原型链继承

子类型的原型为父类型的一个实例对象

function Parent() {    this.name = 'bigStar';    this.colors = ['red', 'blue', 'yellow'];}Parent.prototype.getName = function() {    console.log(this.name)}function Child() {    this.subName = 'litterStar';}// 外围代码: 子类型的原型为父类型的一个实例对象Child.prototype = new Parent();let child1 = new Child();let child2 = new Child();child1.getName(); // bigStarchild1.colors.push('pink');// 批改 child1.colors 会影响 child2.colorsconsole.log(child1.colors); // [ 'red', 'blue', 'yellow', 'pink' ]console.log(child2.colors); // [ 'red', 'blue', 'yellow', 'pink' ]
留神外围代码: Child.prototype = new Parent();

特点:

  1. 父类新增在构造函数下面的办法,子类都能拜访到

毛病:

  1. 来自原型对象的所有属性被所有实例共享,child1批改 colors 会影响child2的 colors
  2. 创立子类实例时,无奈向父类的构造函数传参

2.借助构造函数继承(经典继承)

在子类的构造函数中应用 call()或者 apply() 调用父类型构造函数

function Parent(name) {    this.name = name;    this.colors = ['red', 'blue', 'yellow'];}Parent.prototype.getName = function() {    console.log(this.name)}function Child(name, age) {    // 外围代码:“借调”父类型的构造函数    Parent.call(this, name);    this.age = age;}let child1 = new Child('litterStar');let child2 = new Child('luckyStar');console.log(child1.name); // litterStarconsole.log(child2.name); // luckyStar// 这种形式只是实现局部的继承,如果父类的原型还有办法和属性,子类是拿不到这些办法和属性的。child1.getName(); // TypeError: child1.getName is not a function
留神外围代码: Parent.call(this, name);

特点:

  • 防止援用类型的属性被所有实例共享
  • 创立子类实例时,能够向父类传递参数

毛病

  • 实例并不是父类的实例,只是子类的实例
  • 只能继承父类的实例属性和办法,不能继承原型属性和办法
  • 无奈实现函数复用,每次创立实例都会创立一遍办法,影响性能

3.组合继承:原型链 + 借用构造函数(最罕用)

function Parent(name) {    this.name = name;    this.colors = ['red', 'blue', 'yellow'];}Parent.prototype.getName = function() {    console.log(this.name)}function Child(name, age) {    // 外围代码①    Parent.call(this, name);    this.age = age;}// 外围代码②: 子类型的原型为父类型的一个实例对象Child.prototype = new Parent();Child.prototype.constructor = Child;// 能够通过子类给父类的构造函数传参let child1 = new Child('litterStar');let child2 = new Child('luckyStar');child1.getName(); // litterStarchild2.getName(); // luckyStarchild1.colors.push('pink');// 批改 child1.colors 不会影响 child2.colorsconsole.log(child1.colors); // [ 'red', 'blue', 'yellow', 'pink' ]console.log(child2.colors); // [ 'red', 'blue', 'yellow' ]
留神外围代码: Parent.call(this, name);Child.prototype = new Parent();

特点

  • 交融了原型链继承和借用构造函数的长处,称为JavaScript中最罕用的继承模式。

    毛病

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

    • 一次是设置子类型实例的原型的时候 Child.prototype = new Parent();
    • 一次是创立子类型实例的时候 let child1 = new Child('litterStar');, 调用 new 会执行 Parent.call(this, name);,此时会再次调用一次 Parent构造函数

4. 原型式继承 (Object.create)

借助原型能够基于现有办法来创建对象,var B = Object.create(A) 以A对象为原型,生成A对象,B继承了A的所有属性和办法。

const person = {    name: 'star',    colors: ['red', 'blue'],}// 外围代码:Object.createconst person1 = Object.create(person);const person2= Object.create(person);person1.name = 'litterstar';person2.name = 'luckystar';person1.colors.push('yellow');console.log(person1.colors); // [ 'red', 'blue', 'yellow' ]console.log(person2.colors); // [ 'red', 'blue', 'yellow' ]
留神外围代码: const person1 = Object.create(person);

特点

  • 没有严格意义上的构造函数,借助原型能够基于已有对象创立新对象

    毛病

  • 来自原型对象的所有属性被所有实例共享,person1批改 colors 会影响person2的 colors,这点跟原型链继承一样。

5. 寄生式继承

创立一个用于封装继承过程的函数,该函数在外部以某种形式来加强对象

function createObj (original) {    // 通过调用函数翻新一个新对象    var clone = Object.create(original);    // 以某种形式来加强这个对象    clone.sayName = function () {        console.log('hi');    }    // 返回这个对象    return clone;}

毛病: 每次创建对象都会创立一遍办法,跟借助构造函数模式一样

6.寄生组合式继承(最现实的)

咱们能够先回顾一下JavaScript最罕用的继承模式: 组合继承(原型链 + 借用构造函数),它的最大毛病是会调用两次父构造函数(Child.prototype = new Parent();let child1 = new Child('litterStar');)。

咱们是否能够想方法是调用一次?能够让 Child.prototype 拜访到 Parent.prototype。

咱们不能间接应用 Child.prototype = Parent.prototype来实现,因为会呈现一些副作用,你可能在批改 Child.prototype 的时候会批改Parent.prototype

能够应用 Object.create(...)来实现

Object.create MDN上的解释:它会创立一个新对象,应用现有的对象来提供新创建的对象的__proto__
function Parent(name) {    this.name = name;    this.colors = ['red', 'blue', 'yellow'];}Parent.prototype.getName = function() {    console.log(this.name)}function Child(name, age) {    // 外围代码①    Parent.call(this, name);    this.age = age;}// 外围代码②Child.prototype = Object.create(Parent.prototype);Child.prototype.constructor = Child;
留神外围代码: Parent.call(this, name);Child.prototype = Object.create(Parent.prototype);

寄生组合式继承,集寄生式继承和组合式继承的长处,是援用类型最现实的继承范式。

7. ES6 中class的继承

ES6中引入了class关键字,能够通过extends关键字实现继承。

class Parent {}class Child extends Parent {    constructor(name, age, color) {        // 调用父类的constructor(name, age)        super(name, age);        this.color = color;    }    toString() {        return this.color + ' ' + super.toString(); // 调用父类的toString()    }}

class关键字只是原型的语法糖,JavaScript继承依然是基于原型实现的。

参考

  • JavaScript常见的六种继承形式
  • JS继承的几种形式
  • JavaScript深刻之继承的多种形式和优缺点
  • ECMAScript6入门之 Class的继承