js 实现继承的方法和原理

195 阅读4分钟

原型链实现继承

  • 核心: 子类原型等于父类的实例, 修改了原型对象
  • 缺点: 多个实例对象操作引用类型数据, 数据会被覆盖
  • 原理: 修改了实例对象的原型对象, 改变了原型链的指向
function Person () {
    this.args = ['red', 'yellow']
    this.name = 'parent'
}

function Child () {
    this.name = 'child'
}

// 修改子类的原型
Child.prototype = new Person()
// 但是这样方法, 会修改子类原型对象身上的 constructor 属性,我们需要重新执行子类构造函数
Child.prototype.constructor = Child
// 实现继承
const child = new Child()

构造函数实现继承

  • 核心: 使用 call 方法, 显示的改变父类的 this 指向子类
  • 缺点: 只能继承父类实例身上的属性和方法, 并且每一个子类身上都这些属性和方法的副本, 影响性能
  • 原理: 显示的让父类的 this 指向子类
function Person () {
    this.colors = ['blue', 'pink']
    this.name = 'parent'
}

Person.prototype.get = function  () {
    console.log(this)
}

function Child () {
    // 调用父类的方法
    Person.call(this)
    this.name = 'child'
}

const child = new Child()

组合式实现继承

  • 核心: 修改原型链的指向, 父类实例属性和方法绑定给子类
  • 缺点: 对象身上有父类的实例属性和方法, 同时原型身上也绑定了一份属性和方法
  • 原理: 修改原型链指向, 将父类实例属性和方法绑定给子类, 这样每次调用会先获取实例对象身上的属性, 如果没有再找原型链身上的方法,
function Person () {
    this.colors = ['bule', 'pink']
    this.name = 'parent'
}

Person.prototype.get = function () {
   console.log(this)
}

function Child () {
    // 将父类属性和方法绑定给子类
    Person.call(this)
    this.name = 'child'
}

Child.prototype = new Person()
Child.prototype.constructor = Child
const child = new Child()

原型式实现继承

  • 核心: 使用 Object.create() 方法, 以一个对象为原型, 继承这个对象身上的方法
  • 缺点: 多个实例对引用类型数据进行操作, 会导致数据覆盖, 同时他无法传递参数
  • 原理: 使用 Object.create() 方法, 以一个对象作为对象原型继承
const obj = {
    colors: ['blue', 'white'],
    name: 'parent'
}

const instance = Object.create(obj) // 会以传入的对象作为原型对象创建一个实例

寄生实现继承

  • 核心: 在原型式继承的基础上,增强对象, 返回增强对象
  • 缺点: 和原型式继承一致, 如果多个实例对引用类型进行操作,会覆盖数据, 也无法传递参数
  • 原理: 在原型式继承的基础上, 增强对象
// 用于增强对象
function strongObj(obj){
    const temp = Object.create(obj)
    temp.get = function () {
       console.log(this)
    }
    return temp // 返回这个增强对像
}

const obj = {
    colors: ['white', 'black'],
    name: 'parent'
}

// 使用寄生式继承
const instance = strongObj(obj)

组合寄生实现继承 (成熟的方法, 库也在使用)

  • 核心: 原型链继承 + 构造函数继承 + 寄生式继承
  • 缺点: 实现复杂, 对新手不友好, 原型链有污染的风险, 如果修改过父类,那么整个原型链都会被污染, 有性能消耗, 依然存在, 拷贝以及修改原型等操作
  • 原理: 修改原型链的指向, 在实例身上添加属性, 在原型式寄生的基础上增强对象
function strongObj (Child, Parent) {
    // 使用父构造函数的原型对象作为对象原型
    const prototype = Object.create(Parent.prototype)
    prototype.constructor = Child
    // 实现原型链的继承
    Child.prototype = prototype    
}

function Person () {
    this.colors = ['bule', 'pink']
    this.name = 'parent'
}

Person.prototype.sayHi = function () {
    console.log(this)
}

function Child () {
    // 实现构造函数式继承
    Person.call(this)
    this.name = 'child' 
}

// 实现寄生式继承, 实现构造函数的加强
strongObj(Child, Person)
const instance = new Child()

混入式实现继承

  • 核心: 使用 Object.assign 方法,复制多个对象原型上的方法和属性到目标对象身上
  • 缺点: 代码可读性差, 原型链容易造成污染,在合并时, 如果出现同名的方法和属性, 后面的会覆盖前面的
  • 原理: 使用 Object.assign 方法复制原型对象身上的方法, 主要是拷贝原型上的属性和方法
function Parent () {
    this.colors = ['blue', 'pink']
    this.name = 'parent'
}

Parent.prototype.sayHi = function () {
    console.log('hi')
}

function Child () {
    this.name = 'child'
}

Child.prototype.sayHello = function () {
    console.log('hello')
}

function Son () {
    // 需要在实例生上添加父类的属性和方法
    Parent.call(this)
    Child.call(this)
}

// 将父类的属性和方法拷贝到子类的原型上
const prototype = Object.create(Parent.prototype)
Object.assign(prototype, Child.prototype)
Son.prototype = prototype
Son.prototype.constructor = Son
const instance = new Son()

Es6 class extends 继承

  • 核心: 使用 es6 提供的关键字 extends 以及 super 关键字 实现继承,
  • 缺点: 和寄生组合式一致, 他的内部实现就是这种方式
  • 原理: 使用寄生组合式进行实现
class ParentClass {
    colors: ['pink']
    
    constructor (name) {
        this.name = name
    }
    
    sayHi () {
         console.log('hi')
    }
}

class ChildClass extends ParentClass {
    colors: ['blue']
    
    constructor (name) {
        super(name)
    }
}

const instance = new ChildClass('ldh')