js继承7种方式详解,原来继承如此简单!

121 阅读3分钟

继承,面向对象三大特性之一,在讨论面向对象编程就离不开继承。继承在js编程中对函数复用性起到了举足轻重的地位。尤其在class类中等面向对象编程的领域中,继承这一特性大大节省了开发者在开发中的时间。 首先来讲js继承的实现方式:

原型链继承

说到原型链,原型链可以理解为以下:

截屏2022-07-30 下午12.30.34.png

原型链可以理解为由__proto__组成的原型关系,以上图片红色线条就代表原型链,以上也解释了为什么

  • Object instanceof Function === true
  • Function instanceof Object === true
  • Function.[[prototype]] === Function.prototype

原型链继承代码

function SuperType() {
  this.parent = true;
  this.parentFunction = function() {
    return this.parent;
  }
}
//原型链继承
function childType() {}
childType.prototype = new SuperType();
let result = new childType();
console.log(result.parentFunction()); //true

childType通过prototype继承了SuperType的属性和方法。 确定是否为原型链方法一般由两种形式。

  • 第一种是通过instanceof判断原型链上是否有对应对象
  • 第二种是通过调用对象的prototype.isPrototypeof(实例对象)

两种方法都会返回布尔值

缺点

  • 父类的引用实例会在子类实例中共享
  • 子类无法向父类方法传参

盗用构造函数继承(经典继承)

思路:在子类构造函数调用父类构造函数

实现:

function SuperType(name) {
  this.name = name;
}

function childType() {
  //实现继承并传递参数
  SuperType.call(this, "Bob");
}

let result = new childType();
console.log(result.name); //Bob

优点:

  • 可以向父类属性传参

缺点:

  • 无法继承父类方法
  • 构造函数难以重复使用

组合继承(伪经典继承)

思路:通过原型链继承方法,通过构造函数继承实例属性

实现:

function SuperType(name) {
  this.name = name;
}

SuperType.prototype.printName = function() {
  console.log(this.name);
}

function childType() {
  // 继承属性
  SuperType.call(this, "Bob");
}

//继承方法
childType.prototype = new SuperType();

let result = new childType();
console.log(result.name); //Bob
result.printName(); //Bob

优点

  • 避免共享父类实例的同时继承了父类方法

缺点:

  • 调用了两次父类函数

原型式继承

思路:本质上是对原始对象的克隆,通过创建一个新对象实现继承。主要通过object.create或者通过改变原型而创建新对象的函数来实现。

实现:

let person = {
  name: "Luce",
}

function object(newObject) {
  function F() {};
  F.prototype = newObject;
  return F;
}

// 实现继承
let result = object(person);
// 以下效果和上面相同
//let result = Object.create(person);
result.name = "Bob";


console.log(result.name); //Bob

实际相当于对象的浅拷贝,通过函数的原型绑定对象

缺点

  • 父类的引用实例会在子类实例中共享
  • 子类无法向父类方法传参

寄生式继承

思路:通过内部创建一个新对象,并增强(添加新方法或者属性)新对象,然后返回这个新对象

实现:

let person = {
  name: "Bob",
}
function createObject(object) {
  let newObject = Object.create(object);
  // 寄生
  newObject.printName = function() {
    console.log(this.name);
  };
  return newObject;
}

let result = createObject(person);
result.printName(); //Bob

缺点:

  • 构造函数难以重复使用

寄生式组合继承

思路:使用寄生式继承的形式将父类原型以新对象再次创建,然后将新对象赋值给子对象

实现:

function changeChildType(childType, superType) {
  // 获取父类方法,并将父类方法通过原型链赋值给子类
  let newObject = Object.create(superType.prototype); 
  newObject.constructor = childType; //解决由于重写原型导致childType的constructor丢失
  childType.prototype = newObject;
}

function SuperType(name) {
  this.name = name;
}

SuperType.prototype.printName = function() {
  console.log(this.name);
}

function childType() {
  // 继承属性
  SuperType.call(this, "Bob");
}

changeChildType(childType, SuperType);

let result = new childType();
console.log(result.name); //Bob
result.printName(); //Bob

优点:

  • 只调用一次父类方法
  • 不改变原型链

Class继承(类继承)

思路:es6类支持单继承,使用extends,就可以继承任何拥有[[Construct]]和原型的对象。既可以继承类,也可以继承普通的构造函数。

实现:类继承实现原理也是使用的原型链,通过构造函数的形式,创建类似class类的语法

class ParentClass {}

// 继承类
class Bus extends ParentClass {}

function ParentFunction() {}

// 继承普通构造函数
class Child extends ParentFunction {}

class的super

  • 在执行后会调用父类函数,并将实例返回给this
  • 只能在派生类构造函数和静态方法使用
  • 不能单独引用,要么用它调用构造函数,要么用它引用静态方法
  • super()如同调用构造函数,如果需要给父类构造函数传参,需要手动传入