js继承的6种写法

2021-10-27 23:33:33

#前端#javascript

35

前言

  • 写这种文章总感觉有点怪怪的,就像是孔乙己在说茴字有几种写法一样。
  • 开玩笑的,像这种基础知识还是很重要的

正文

  • 首先可以用下面的这一张图来进行总结 Description
  • 基于上面这张图,分别总结一下js的六种继承方式

原型链继承

 function Parent() {
    this.name = 'parent';
    this.play = [1, 2, 3]
  }
  function Child() {
    this.type = 'child';
  }
  Child.prototype = new Parent();
  console.log(new Child());
  • 根据红宝书第4版p238中所说,ECMA-262把原型链定义为ECMA的主要继承方式
  • 基本思想
    • 通过原型继承多个引用类型的属性和方法
  • 缺点
    • 一旦改变了子类中该父类的属性,那么继承了该父类的其余子类的继承属性值也会跟着改变
  • 解决方法
    • 子类每继承一次,就多new一个父类
  • 当然缺点也是很明显的,那就是性能消耗比较大
  • 其它原型与继承相关知识点
    • 使用instanceOf关键字可以确认原型和实例的关系,如果一个实例的原型链中出现过响应的构造函数,则instanceOf返回true

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

function Parent1(){
    this.name = 'parent1';
  }
  function Child1(){
    Parent1.call(this);
    this.type = 'child1'
  }
console.log(new Child1);
  • 原理:利用call将父类构造函数引入子类函数
  • 优点
    • 传递参数:可以在子类构造函数中向父类构造函数传参
  • 缺点
    • 子类不能访问父类原型上定义的方法
    • 必须在构造函数中定义方法,因此函数不能重用

组合继承

  • 将上面的两种继承方式组合起来,就成了组合继承的方式
function Parent(value) {
  this.val = value
}
Parent.prototype.getValue = function() {
  console.log(this.val)
}
function Child(value) {
  // 继承属性
  Parent.call(this, value)
}
// 继承方法
Child.prototype = new Parent()

const child = new Child(1)

child.getValue() // 1
child instanceof Parent // true
  • 以上继承的方式核心是
    • 利用 Parent.call(this) 继承父类的属性
    • 改变子类的原型为 new Parent() 来继承父类的函数。
  • 优点
    • 构造函数可以传参,不会与父类引用属性共享,可以复用父类的函数
  • 缺点
    • 就是在继承父类函数的时候调用了父类构造函数,导致子类的原型上多了不需要的父类属性,存在内存上的浪费

原型式继承

function object(o){
    // 临时构造函数
    function F() {}
    // 将传入的对象赋值给这个构造函数的原型
    F.prototype = o
    // 返回临时类型的实例
    return new F();
}
  • 本质上对传入的对象进行了一次浅拷贝
  • 所有实例都继承了原型上的属性
  • ECMAScript5增加了object.create()方法将原型式继承的概念规范化了,现在使用原型式继承使用object.create()即可
  • 和原型链模式类似,子类实例会共享父类实例的属性,一旦改变了子类中该父类的属性,那么继承了该父类的其余子类的继承属性值也会跟着改变

寄生式继承

  • 与原型式继承类似,其实就是给原型式继承套了个壳子
  • 思路
    • 创建一个实现继承的函数,以某种形式增强对象,最后返回对象
function createAnother (origin){
    let clone = object(origin)  // 通过调用对象来创建一个新对象
    clone.sayHi = function(){  // 继承中新增的方法(以某种形式增强对象)
        console.log(\"hi\")
    }
    return clone;  // 返回该对象
}

let person = {
    name:\"Nicholas\",
    friends:[\"Shelby\",\"Court\",\"Van\"]
}
let anotherPerson = createAnother(person)
anotherPerson.sayHi()  // hi
  • 与构造函数模式类似,会导致函数难以重用

寄生组合继承

  • 对组合继承进行了优化,组合继承缺点在于继承父类函数时调用了两次构造函数,一次在创建子类原型时调用,另一次则在子类构造函数中又调用了一次,
  • 本质上子类原型最终是要包含超类对象的所有实例属性
  • 寄生:
    • 在函数内返回对象然后调用
  • 组合:
    • 函数的原型等于另一个实例
    • 在函数中用apply或者call引入另一个构造函数,可传参 
 function Parent () {
    this.name = 'parent';
    this.play = [1, 2, 3];
  }
  function Child() {
    Parent.call(this);  // 组合式思想利用call引入父类构造函数
    this.type = 'child';
  }
  Child.prototype = Object.create(Parent.prototype);  // 寄生式思想返回对象
  Child.prototype.constructor = Child;  // 将父类的构造函数设置为子类,使其只调用一次子类构造函数,避免创建父类中无用的属性,提高继承效率

// 具体使用
let a = new Child()
console.log(a)  // Child { name: 'parent', play: [ 1, 2, 3 ], type: 'child' }
let b = new Child()
b.name = 'child'
console.log(a)  // Child { name: 'parent', play: [ 1, 2, 3 ], type: 'child' }
console.log(b)  // Child { name: 'child', play: [ 1, 2, 3 ], type: 'child' }
  • 优点
    • 只调用一次构造函数,提高效率
    • 原型链保持不变,instanceof操作符和isPrototypeOf()方法仍然有效
    • 子类更改属性不会互相影响
  • 可以说寄生组合式继承是引用类型继承的最佳模式

ES6的继承写法

class Parent {
  constructor(value) {
    this.val = value
  }
  getValue() {
    console.log(this.val)
  }
}
class Child extends Parent {
  constructor(value) {
    super(value)
    this.val = value
  }
}
let child = new Child(1)
child.getValue() // 1
child instanceof Parent // true
  • 相当于语法糖,原理与寄生组合继承模式类似,使其更符合面向对象的写法