JS常见的继承方式总结(面试常考)

在ES6出现之前,一般是使用构造函数来实现批量的创建对象,而谈到对象,这就会想到面向对象式编程,面向对象有三大特征:封装、继承、多态,在js中主要以实现继承来体现出面向对象的思维方式。

目录

以下整理出了js常见的6种继承方式

1.原型链继承

2.利用构造函数继承 (借助call方法)

3.组合式继承(将前两种结合) 

 4.原型式继承

 5.寄生式继承

6.寄生组合式继承 


以下整理出了js常见的6种继承方式

1.原型链继承

原型链继承是通过原型对象可以被覆盖的特点,直接用父类的实例对象覆盖子类的原型对象实现的。

        //父类
        function Father(name) {
            this.name = name;
            this.eats = ['香蕉']
            this.sayName = function () {
                console.log(this.name);
            }
        }
        Father.prototype.age = '10'

        //子类
        function Son(name) {
            this.name = name
            this.sonname = 'jack'
        }
        var father = new Father('mk')
        Son.prototype = new Father()
        var son1 = new Son('son')
        var son2 = new Son()
        son1.sayName();
        console.log('son1', son1);
        son1.eats.push('苹果')
        console.log('son2', son2);

可以看出两个子实例都是吧父类的实例当做了自己的原型对象,实现了继承,但是我们向son1中的eats属性中push了一个‘苹果’后,输出son2的时候,它的原型里也有了一个‘苹果‘,所以存在一个弊端。以下是对原型链继承的总结:

重点:让子实例对象对象指向父实例对象

特点:子实例对象可继承的属性有:

 缺点:1.所有子实例化对象共用一个原型对象,如果有一个发生改变则全部发生改变

            2.子实例对象无法向父实例对象传参

            3.父类所有的引用数据(对象,数组)会被子类共享,更改一个子类的数据,其他子类数据会发生改变

2.利用构造函数继承 (借助call方法)

构造函数是通过在子类中使用call方法改变父类的this指向,将父类的this指向子类,实现继承。

        function Parent1(name) {
            this.eats = ['苹果']
            this.name = name
        }
        Parent1.prototype.getName = function () {
            return this.name
        }
        function Child1(name) {
            Parent1.call(this, name) //将Parent1的this指向Child1
            this.type = 'child1'
        }

        var child = new Child1('qwe')
        child.eats.push('香蕉') 
        var child1 = new Child1('abc')

        console.log(child.name);
        console.log(child);
        console.log(child1);

        child.getName()

 可以看出,这种继承方式没有改变子类原型,而是在实例化的时候将父类的成员直接放在子类的实例成员上。在child中eats添加了一个‘香蕉’后,child1中的eats属性未受到影响。 

不过由于没有改变子类原型,父类原型中的成员子类就无法获取、调用。产生报错

 特点:

1.只继承父类构造函数的成员,没有继承父类原型的成员

2.解决了原型链继承的缺点

3.可以继承多个构造函数的属性(call多个)

4.子实例可以向父实例传参

优点:

父类的引用类型的数据不会被子类共享,不会互相影响

缺点:

1.只继承父类构造函数的成员,没有继承父类原型的成员,子类无法访(Parent1.prototype)

2.无法实现构造函数的复用(每次用,每次都要改变父构造函数的this指向)

3.每个新实例都有父类构造函数的副本,造成内存臃肿

3.组合式继承(将前两种结合) 

这种方式结合了前两种继承方式的优缺点,结合起来的继承,代码如下

        function Father(name) {
            this.name = name
            this.abc=123
        }
        Father.prototype.aaa = function (aaa) {
            console.log(aaa);
        }
        function Son(name) {
            Father.call(this, name)  //构造函数继承
            this.type = 'son'
        }
        Son.prototype = new Father() //原型链继承
        var s = new Son('jack')
        console.log(s);
        s.aaa('aaa的参数')

优点是将前两种结合,子实例既有父类的属性和方法,而且可以调用父类原型中的方法。

缺点也显而易见:在子实例中,有两份一样的属性 abc 和 name,对性能有一定的影响。

优点:

        1.父类可以复用

        2.父类构造函数中的引用属性数据不会被勾选

缺点:

        会调用两次父类的构造函数,会有两份一样的属性和方法,会影响性能

 4.原型式继承

在 ES5 里面有一个 Object.create 方法,这个方法接收两个参数:一是用作新对象原型的对象、二是为新对象定义额外属性的对象(可选参数)。

        var person = {
            name: 'person',
            eats: ['苹果'],
            getName: function () {
                return this.name
            }
        }

        var person1 = Object.create(person) //将peeson作为person1的原型

        person1.name = 'Jack'
        person1.eats.push('香蕉')
        person1.__proto__.getEats = function () {
            return this.eats
        }
        console.log(person1);
        console.log(person1.getEats());

        var person2 = Object.create(person)

        person2.eats.push('梨子')

        console.log(person1.name === person1.getName());
        console.log(person2.name);
        console.log(person1.eats);
        console.log(person2.eats);

通过 Object.create 这个方法可以实现普通对象的继承,不仅仅能继承属性,同样也可以继承方法

getName()。

 可以看出,当我们在实例中对父级引用数据中添加了了值后,其他的实例中也出现了改变,这种继承方式的缺点也很明显了

缺点:多个实例的引用类型属性指向相同的内存,更改一个子类的数据,其他子类数据会发生改变

 5.寄生式继承

使用原型式继承可以获得一份目标对象的浅拷贝,然后利用这个浅拷贝的能力再进行增强,添加一些方法,这样的继承方式就叫作寄生式继承。

虽然其优缺点和原型式继承一样,但是对于普通对象的继承方式来说,寄生式继承相比于原型式继承,可以在父类的基础上添加更多的方法。

        var person = {
            name: 'person',
            eats: ['苹果'],
            getName: function () {
                return this.name
            }
        }

        function clone(obj) { //利用函数对继承后的实例添加方法
            var clone = Object.create(obj)
            clone.getEats = function () {
                return this.eats
            }
            return clone
        }
        var person1 = clone(person)
        console.log(person1.getName());
        var person2 = clone(person)

        console.log(person1);
        person2.eats.push('香蕉')
        console.log(person1.getEats());
        console.log(person2.getEats());

我们可以看到 person1 是通过寄生式继承生成的实例,它不仅仅有 getName 的方法,而且可以看到它最后也拥有了 getEats 的方法。

 

从最后的输出结果中可以看到,person1 通过 clone 的方法,增加了 getEats 的方法,从而使 person1 这个普通对象在继承过程中又增加了一个方法,这样的继承方式就是寄生式继承。

优点:在继承的过程中可以添加一些方法。

缺点:

1.在上面第三种组合继承方式中提到了一些弊端,即两次调用父类的构造函数造成浪费

2.与原型式继承一致:多个实例的引用类型属性指向相同的内存,更改一个子类的数据,其他子类数据会发生改变

6.寄生组合式继承 

 目前最优的继承方案,在前面这几种继承方式的优缺点基础上进行改造,得出了寄生组合式的继承方式,也是所有继承方式里面相对最优的继承方式

        function Father(name) {
            this.name = name
            this.abc = 123
            this.eats = ['苹果']
        }
        Father.prototype.aaa = function (aaa) {
            console.log(aaa);
        }
        function Son(name) {
            Father.call(this, name)  //构造函数继承
            this.type = 'son'
        }

        var Fn = function () { }
        Fn.prototype = Father.prototype //将父类原型赋值给Fn

        Son.prototype = new Fn() //改变为Fn的实例
        var s = new Son('jack')
        var s1 = new Son('mk')
        s1.eats.push('香蕉')
        console.log(s);
        console.log(s1);
        s.aaa('aaa的参数')

可以看出来,这种寄生组合式继承方式,基本可以解决前几种继承方式的缺点,较好地实现了继承想要的结果,同时也减少了构造次数,减少了性能的开销

 整体看下来,这六种继承方式中,寄生组合式继承是这六种里面最优的继承方式。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

俗人844

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值