JavaScript中常见的7种继承方式

举例

• 例子: 比如说我们生活中的汽车就是一个类
• 轿车 和 货车 分别 继承汽车的属性 只不过 轿车 后面加的是 后备箱, 货车后面接的是大货箱, 使得轿车 和 货车 具备与父类不同的方法
• 定义: 继承可以是的子类别具有父类的各种方法和属性

第一种 原型链继承

原型链继承是比较常见的继承方式之一,其中涉及的构造函数,原型和实例三者之间的关系
每一个构造函数都有一个原型对象
原型对象有包含一个指向 构造函数的指针
而实例包含一个原型对象的指针

        function Parent1() {
            this.name = 'parent1';
            this.play = [1, 2, 3]
        }
        function Child1() {
            this.type = 'child2'
        }
        Child1.prototype = new Parent1()
        console.log(new Child1())
        
        var s1 = new Child1()
        var s2 = new Child1()
        s1.play.push(4)
        console.log(s1.play, s2.play)

原型继承会有一个问题
• 明明我只改变了 s1 为什么 s2 也发生了改变
• 因为两个实例使用的是一个原型对象,因此内存空间是共享的,当一个发生变化的时候,另一个也随之进行了变化

缺点:
1.原型链中引用类型的属性会被所有实例共享的,即所有实例对象使用的是同一份数据,会相互影响。

function Person(){
            this.colors = ["red", "green", "pink"];
        }
        function Student(){
        }
        Student.prototype = new Person();
        var a1 = new Student();
        var a2 = new Student();
        a1.colors.push("white");
        console.log(a2.colors);    //"red", "green", "pink" ,"white"

2.在创建子类的实例时,不能向超类传参(无法向父级构造函数传参)

第二种 构造函数继承 (经典继承 借助 call)

核心思想是:在子级构造函数中调用父级构造函数。

function Parent1() {
    this.name = 'parent1'
}
Parent1.prototype.getName = function () {
    return this.name
}
function Child1() {
    Parent1.call(this);
    this.type = 'child1'
}
let child = new Child1()
console.log(child)
console.log(child.getName())

• 子类拿到父类的属性值,避免了原型链继承的弊端,父类原型对象前,出现自己定义的方法,那么子类将无法继承这些方法

构造函数的优缺点
• 父类的引用属性不会被共享,解决了原型链继承的弊端,但随之而来的缺点也比较明显, 只能继承父类的实例属性和方法, 不能继承原型属性和方法

第三种 组合继承

组合继承 = 原型链 + 借用构造函数。
取其长避其短:共享的用原型链,各自的借用构造函数
核心思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承,这样,既通过在原型上定义方法实现了函数复用,又能保证每个实例都有它自己的属性。

function Parent3 () {
    this.name = 'parent3';
    this.play = [1,2,3]
}
Parent3.prototype.getName = function () {
    return this.name
}
function Child3 () {
    Parent3.call(this);
    this.type = 'child3'
}
Child3.prototype = new Parent3()
Child3.prototype.constructor = Child3()
var s3 = new Child3()
var s4 = new Child3()
s3.play.push(4)
console.log(s3.play, s4.play)
console.log(s3.getName())
console.log(s4.getName())

parent3 多构造了一次 会造成 内存上额外的开销
优点:融合原型链继承和构造函数的优点,是JavaScript中最常用的继承模式
缺点:调用了两次父类构造函数
(组合继承最大的问题是无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部)

第四种 原型式继承

let parent4 = {
    name: 'parent4',
    friends: ['p1','p2','p3'],
    getName: function () {
        return this.name
    }
}
let person4 = Object.create(parent4)
person4.name = 'tom'
parent4.friends.push('jerry')
let person5 = Object.create(parent4)
person5.friends.push('lucy')
console.log(person4.name)
console.log(person4.name === person4.getName())
console.log(person5.name)
console.log(person4.friends)
console.log(person5.friends)

• 不仅能继承属性 还能继承 getName 方法
• 但是 引用数组是共享的
• Object.create() 可以对一些对象实现浅拷贝的
• 那么这种继承方式的缺点也很明显
• 实例的引用,类型属性指向相同内存, 存在篡改的可能

第五种 寄生式继承

• 原型式继承可以获得一份目标对象的浅拷贝,然后利用这个浅拷贝的能力在进行增添一些方法
优点:
• 寄生式继承相比原型式继承,还是在父类基础上添加了更多的方法
缺点:跟借用构造函数类似,调用一次函数就得创建一遍方法,无法实现函数复用,效率较低。

let parent5 = {
    name: 'parent5',
    friends: ['p1','p2','p3'],
    getName() {
        return this.name
    }
}
function clone (original) {
    let clone = Object.create(original)
    clone.getFriends = function () {
        return this.friends
    }
    return clone
}
let person5 = clone(parent5)
console.log(person5.getName())
console.log(person5.getFriends())

第六种 寄生组合式继承

利用组合继承和寄生继承各自优势
寄生组合式继承,是集寄生式继承和组合继承的优点与一身,主要是通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。它的缺点是两次调用父级构造函数,一次是在创建子级原型的时候,另一次是在子级构造函数内部,那么我们只需要优化这个问题就行了,即减少一次调用父级构造函数,正好利用寄生继承的特性,继承父级构造函数的原型来创建子级原型

function clone(parent,child) {
    // 这里用 Object.create 就可以减少组合式继承中多进行一次构造的过程
    child.prototype = Object.create(parent.prototype)
    child.prototype.constructor = child
}
function Parent6 () {
    this.name = 'parent6'
    this.play = [1,2,3]
}
Parent6.prototype.getName = function () {
    return this.name
}
function Child6() {
    Parent6.call(this)
    this.friends = 'child5'
}
clone(Parent6,Child6)
Child6.prototype.getFriends = function () {
    return this.friends
}
let person6 = new Child6()
console.log(person6)
console.log(person6.getName())
console.log(person6.getFriends())

优缺点:组合继承优点、寄生继承的优点,目前JS继承中使用的都是这个继承方法

第七种 可以通过 ES6 的 extends 关键是实现继承

class Person {
    constructor(name) {
        this.name = name
    }
    // 原型方法
    // 既 Person.prototype.getName = function() {}
    // 可以简写成 getName(){}
    getName () {
        console.log('Person',this.name)
    }
}
class Gamer extends Person {
    constructor(name,age) {
        super(name);
        this.age = age
    }
}
const asuna = new Gamer ('Asuna', 20)
asuna.getName() // 成功访问到父类的方法

extends 底层也是使用的寄生组合式继承

总结

在这里插入图片描述
写作意图1


  1. 本人写本篇文章主要是记录平时所学,如有侵权请联系本人删除。
    本文主要借鉴了以下出处:
    我的老师Zoe 参考博客平台:语雀;
    另外感谢另一位大佬
    作者:QQ音乐的百香果
    链接:https://www.jianshu.com/p/28954fccfe11
    来源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 ↩︎

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值