JavaScript中的继承模式

本文是前端学习笔记的第六篇,对应web前端开发JavaScript精英课js的第21课时,本篇主要写关于JS中的四种继承方式,这四种也可以说是整个JS继承的发展史了

目录

JavaScript中的继承发展史

1. 原型链

2. 借用构造函数(通过call/apply)

3. 共享原型

4. 圣杯模式


JavaScript中的继承发展史

JS继承一共可分为四种,根据不断的发展进化由第一种进化到如今普遍使用的第四种

  • 原型链
  • 借用构造函数(通过call/apply)
  • 共享原型
  • 圣杯模式

 

1. 原型链

通过原型链的方式实现继承,是最初级版的继承,这种继承方式存在一定的弊端,譬如我只想继承某个构造函数的原型对象的一些属性,但却不得不一并继承了属于构造函数自己的一些属性

 <script>

        Father.prototype.Firstname = "周";

        function Father() {
            this.englishName = "I am Father";
        }

        var father = new Father();
        Son.prototype = father;

        function Son() {

        }

        var son = new Son();

</script>

son此时只想继承Father的FirstName,但却因为原型链的缘故,不得不继承了原本不需要的属性EnglishName,这显然不是我们所期望的,因此便有了后来的一系列继承方式

 

2. 借用构造函数(通过call/apply)

通过call/apply函数,我们可以借用别的构造函数来为对象增添属性。先复习一下call和apply的用法,二者都是用于重定义this的指向,区别是前者是通过 函数.call(对象引用,args0,args1...)的方式可以把对象引用取代前面函数中的this的位置进行操作,后者是通过函数.apply(对象引用,args[n])的方式改变函数中的this引用,也即是参数用数组来表示

接下来看下面一个例子

<script>

        function Car(weight,height,speed) {
            CarFactory.call(this,weight,height,speed);
        }
        
        function CarFactory(weight,height,speed) {
            this.weight = weight;
            this.height = height;
            this.speed = speed;
        }

        var car = new Car('4900','160','1km/s');

</script>

此时在创建对象car时通过借用构造函数CarFactory,为自己添加了属性weight,height,speed,当然准确来说这不能算继承,只是借用别的构造函数为自己增加属性,且这种方式也有缺点,若过多使用会造成编码效率降低,代码冗余

 

3. 共享原型

在第一第二种继承方式都不理想的情况下,又发展出了第三种继承模式:共享原型

看下面一个例子

<script>

        Father.prototype.firstName = "周";

        function Father() {
            this.englishName = "I am Father";
        }


        Son.prototype = Father.prototype;

        function Son() {

        }

        var son = new Son();

</script>

共享了Father.prototype与Son.prototype后,此时对象son相比第一种原型链的情况有了很大的改善,不再会继承无关的属性englishName,因为这个属性是属于用构造函数Father创建的对象,而此时并没有用构造函数Father创建对象,只是共享了彼此的原型,也就是只是获得了属性firstName

但是这种做法仍有缺陷,再看下面一个例子

<script>

        Father.prototype.firstName = "周";

        function Father() {
            this.englishName = "I am Father";
        }


        Son.prototype = Father.prototype;

        function Son() {

        }

        var son = new Son();

        Son.prototype.firstName = '陈';

        console.log(Father.prototype.firstName);   // 陈

</script>

此时试图修改Son的原型对象上的firstName属性,但是因为共享原型的缘故,Father.prototype和Son.prototype的引用是相同的,那么修改其中一个的属性就会修改另外一个对象的属性,这显然不是我们希望看到的,因此,便提出了新的解决方案

 

4. 圣杯模式

既然修改原型对象会导致共享的原型对象的属性改变,那么是否可以在二者之间建一个中间层隔开一下呢,基于这样的考虑,圣杯模式便诞生了,看下面一个例子

<script>

        function inherit(Orign,Target) {
            function F () {}
            F.prototype = Orign.prototype;
            Target.prototype = new F();
            Target.prototype.constructor = Target;
            Target.prototype.uber = Orign.prototype;
        }

        function Son() {
            
        }

        Father.prototype.firstName = '周';

        function Father() {
            
        }

        inherit(Father,Son);

        var son = new Son();

</script>

原本的共享原型是直接Target.prototype = Orign.prototype,而这里通过一个构造函数F,作为一个中间层,使得Target.prototype修改的用构造函数 f 造出来的对象的属性,而不会干预到Father.prototype的属性值,这里再提提另外两行特别的代码

  • Target.prototype.constructor = Target
  • Target.prototype.uber = Orign

第一行是修改原型对象的构造器,我们知道,对象本身是没有constructor属性的,这个属性在对象的原型对象身上的,而这里son的原型对象是用构造函数F造出来的对象,本身系统默认给的prototype,其身上同样没有constructor,继续在其原型对象身上找,也就是Father.prototype身上找,终于找到了constructor属性,而constructor属性表示的是对象的构造函数,因此如果没有这句,那么son的constructor便是Father.prototype身上的constructor,修改后,便变为了自己,更符合实际情况

第二行是为Son的原型对象新增一个uber属性,值为Orign.prototype,因为此时已改变了构造器,此时不再知道其共享的到底是哪个原型对象,因此便新增一个属性用于找回这个原型对象

 

当然,还可以更进一步,把圣杯模式写的更加简洁、结构清晰

<script>
        var inherit = (function () {
            var f = function () {}
            return function (Orign,Target) {
                f.prototype = Orign.prototype;
                Target.prototype = new f();
                Target.prototype.constructor = Target;
                Target.prototype.uber = Orign.prototype;
            }
        }());

        function Son() {
            
        }

        Father.prototype.firstName = '周';

        function Father() {
            
        }

        inherit(Father,Son);

        var son = new Son();

</script>

把整个共享原型的过程以立即执行函数的方式表现,代码可读型大大提高,代码复用率也高,最重要的是,避免了对全局变量的污染,推荐平时如果需要用继承就用这种做法

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值