JS-六种继承方式(ES5+ES6)

前五种为ES5继承实现,最后一种ES6

一、原型链继承

原理:让子类的原型对象指向父类实例,当子类实例找不到对应的属性和方法时,就会往它的原型对象,也就是父类实例上找,从而实现对父类的属性和方法的继承

// 父类
function Parent() {
    this.name = 'huohuo'
}
// 在父类原型上添加方法
Parent.prototype.getName = function () {
    return this.name
}
// 子类
function Child() { }

// 继承 Parent
Child.prototype = new Parent() // 让子类的原型对象指向父类实例, 这样一来在Child实例中找不到的属性和方法就会到原型对象(父类实例)上寻找
// 解决重写原型导致的 constructor 丢失问题(增强对象)
Child.prototype.constructor = Child // 根据原型链的规则,顺便绑定一下constructor, 这一步不影响继承, 只是在用到constructor时会需要
// 创建一个实例化对象
const child = new Child() // 然后 Child实例 就能访问到父类及其原型上的 name属性 和 getName方法
child.name          // 'huohuo'
child.getName()     // 'huohuo'

缺点:

  1. 由于所有Child实例原型都指向同一个Parent实例,引用类型的私有属性公有化了, 因此对某个Child实例的父类引用类型做变量修改会影响所有的Child实例
  2. 在创建子类实例时无法向父类的构造函数传参(没有 super 功能)

二、构造函数继承

原理:子类的构造函数中执行父类的构造函数,并为其绑定子类的 this,让父类的构造函数把成员属性和方法都挂到子类的 this 上去,这样既能避免实例之间共享一个原型实例,又能向父类构造方法传参

function Parent(name) {
    this.name = name
}
Parent.prototype.getName = function () {
    return this.name
}
function Child() {
    // 执行父类的构造函数,并修改 this 指向到子类, 使得父类中的属性能够赋到子类的 this 上
    Parent.call(this, 'huohuo') // 既能避免实例之间共享一个原型实例,又能向父类构造方法传参
}
const child1 = new Child()
console.log(child1.name) // huohuo
child1.getName() // 报错, 找不到getName(), 构造函数继承的方式继承不到父类原型上的方法             

解决了原型继承中“引用类型私有属性变公有”、“不可传递参数”两个问题

缺点:继承不到父类原型上的方法(所以不能单独使用咯 ~)

三、组合继承

原理:使用原型链继承原型上的属性和方法,而通过构造函数继承实例属性构造函数+原型对象

function Parent (name) {
    this.name = name
}
Parent.prototype.getName = function () {
    return this.name
}
function Child () {
    // 构造函数继承
    Parent.call(this, 'huohuo') // 既能避免实例之间共享一个原型实例,又能向父类构造方法传参
}
//原型链继承
Child.prototype = new Parent() // 父类的方法可以被子类通过原型拿到
Child.prototype.constructor = Child // 顺带绑定下 constructor (增强对象)

//测试
const child1 = new Child()
const child2 = new Child()
child1.name = 'foo'
console.log(child1.name)          // foo
console.log(child2.name)          // huohuo
child2.getName()                  // 'huohuo'

组合式继承的缺点:每次创建子类实例都执行了两次构造函数(Parent.call() 和 new Paren()),虽然这并不影响对父类的继承,但子类创建实例时,原型中会存在两份相同的属性和方法,这并不优雅

四、寄生式继承

原理:创建一个实现继承的函数,改函数内部以某种形式来增强对象,最后返回对象

function ParasInherit(obj) {
    const child = Object.create(obj) // 通过调用函数创建一个对象
    child.sayHi = function () { // 增强对象
        console.log('HelloHuohuo');
    }
    return child // 基于 obj 对象返回了一个新的对象 (具有 obj 的所有属性和方法)
}

// 测试
const person = {
    name: 'huohuo',
    data: ['3', '180', '50']
}
const inheritPerson = ParasInherit(person)
inheritPerson.sayHi() // HelloHuohuo

缺点:类似构造函数继承,会导致函数难以重用

五、寄生式组合继承(最理想)

为了解决第三种继承提到的构造函数被执行两次的问题, 我们将指向父类实例改为指向父类原型, 减去一次构造函数的执行

function Parent(name) {
    this.name = name
}
Parent.prototype.getName = function() {
    return this.name
}
function Child() {
    // 构造函数继承
    Parent.call(this, 'huohuo') 
}
//原型链继承
// Child.prototype = new Parent()
Child.prototype = Parent.prototype  //将`指向父类实例`改为`指向父类原型`
Child.prototype.constructor = Child

//测试
const child1 = new Child()
const child2 = new Child()
child1.name = 'foo'
console.log(child1.name)          // foo
console.log(child2.name)          // huohuo
child2.getName()                  // 'huohuo'

但这种方式存在一个问题,由于子类原型和父类原型指向同一个对象,我们对子类原型的操作会影响到父类原型,例如给 Child.prototype 增加一个getName()方法,那么会导致 Parent.prototype 也增加或被覆盖一个 getName 方法,为了解决这个问题,我们给 Parent.prototype 做一个浅拷贝

//Child.prototype = Parent.prototype改为 
Child.prototype = Object.create(Parent.prototype)

最后不要忘了把方法封装一下哦!

// 封装一下
function myInherit (child, parent) { // 接受两个参数:父子构造函数
    let prototype = Object.create(parent.prototype) // 创建父类原型的拷贝
    prototype.constructor = child // 解决重写原型导致的 constructor 丢失问题(增强对象)
    child.prototype = prototype // 将新对象赋值给子类原型
}

// 测试
function Parent (name) {
    this.name = name
}
Parent.prototype.getName = function () {
    return this.name
}
function Child () {
    Parent.call(this, 'huohuo')
}
myInherit(Child, Parent)
const child1 = new Child()
console.log(child1.name); // huohuo

六、ES6的class实现继承

原理:ES6类 + 寄生式组合继承

// Class 类继承
class Father {
    constructor(surname) {
        this.surname = surname; // 类的属性声明用 this 即可
    }
    saySurname () {
        console.log('My surname is ' + this.surname); // 访问类的属性要用 this
    }
}
class Son extends Father { // 这样子类就继承了父类的属性和方法
    constructor(surname, firstname) {
        super(surname); // 通过调用 super 来调用父类的构造函数,并初始化父类的属性
        this.firstname = firstname; // 初始化一个子类属性
    }
    sayFirstname () {
        console.log('My firstname is ' + this.firstname);
    }
}
const cc = new Son('ai', 'huohuo');
cc.saySurname(); // My surname is ai
cc.sayFirstname(); // My firstname is huohuo
  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值