JavaScript继承方式及原理

13 篇文章 1 订阅
4 篇文章 5 订阅

继上篇文章深入剖析prototype、constructor、_proto_原理后,接下来整理、理解JS中的继承就水到渠成,一气呵成!

话不多说,上个文章中说过,原型链最主要就是为了解决JS在面向对象中继承功能的实现。所以在js继承中第一种要介绍的方式当然是从原型链继承。过程中会插入我对该种继承产生过的一些疑惑并作出解答,希望这对你的理解能带来帮助

原型链继承:

	    function Father () {
            this.name = 'father';
        }
        Father.prototype.getName  = function () {
            console.log(this.name);
            return this.name;
        };

        function Son () {
            this.age = 12;
        }

        //name属性在son中找不到,则会自动从Son的prototype(即father对象)中寻找
        Son.prototype = new Father(); // 原型链继承

        let son1 = new Son();

在理解这种继承的时候我产生过一个疑惑:既然是原型链继承,为什么不把Son.prototype = Father.prototype?后来仔细想想,这种做法会丧失掉继承中非常重要的特性:多态 。如果不知道继承多态特性的可以去查查。并且子类在原型链做做增删改都会直接影响到父类。这些缺陷都太过恶劣。

然而上面的这种继承方式也不是完美的,他同样存在缺点:

  • 无法继承Father构造函数内的所有东西(无法继承Father构造函数中的name(私有))

先不讲如何改进原型链继承,接下来看常见的第二种继承方式:

构造继承:

		function Father () {
            this.name = 'father';
        }
        Father.prototype.getName  = function () {
            console.log(this.name);
            return this.name;
        };

        function Son () {
            Father.call(this); // 构造继承
            this.age = 12;
        }

        let son1 = new Son();

这种继承方式仅仅是利用Father构造方法,传入son作为Father构造函数中的this。他们的原型链并未发生任何关系。

  • 优点: 利用了父类Father构造函数初始化了son
  • 缺点:未产生原型链上的任何关系
  • 现象:son能继承Father在构造函数内定义的name(私有),但访问不了Father在原型链上的getName属性(公有)

先做个小结:仔细看不难知道,构造继承与原型链继承分别是继承私有和公有两种属性的方式,那么结合形成互补才是两者的改进之道。

由此引出第三种继承:

组合继承:

	    function Father () {
            this.name = 'father';
        }
        Father.prototype.getName  = function () {
            console.log(this.name);
            return this.name;
        };

        function Son () {
            Father.call(this); // ②
            this.age = 12;
        }

        //name属性在son中找不到,则会自动从Son的prototype(即father对象)中寻找
        Son.prototype = new Father(); // 原型链继承  ③
        
        Son.prototype.constructor = Son; // ①

        let son1 = new Son();

代码几乎是将前面第一、二中结合起来,但唯一不同的是:在代码①处:Son.prototype,constructor = Son

产生过疑惑:

  • 为什么增加代码①处:Son.prototype.constructor = Son?
  • 因为Son.prototype = new Father(),Son.prototype指向的是father对象,而father对象本应该是Father()构造函数,增加①代码后岂不是将father的构造函数改为指向Son()?

细细想来:

疑惑一:因为son的prototype发生了改变:son._proto_原本的指向Son()构造函数下的prototype,而这个prototype中的construtor指向Son(),而此时执行Son.prototype = new Father()之后,son._proto_指向了new Father()对象,这时候son丢失了原本的construtor指向Son()的关系【这个关系是有必要继续维持的,例如用到instance of之类的方法就会产生意想不到的结果】

疑惑二:Son.prototype.construtor = Son仅仅是在father对象中新增了construtor属性,而非改变Father()下protytpe下的construtor:通过调试模式下你就可以发现construtor真真实实实在father对象中产生。

  • 优点:能够继承Father的私有(construtor)及公有(prototype)属性
  • 现象:son拥有name属性,并且能够访问Father下的getName属性。

然而这种方式也有美中不足的地方,在继承的过程中Father()被执行了两次【②③】,而②的作用仅仅是为了原型链的继承,所以执行Father()函数中的其他逻辑【如this.name = name】是没有作用并且浪费资源的。但我们前面又讲了不能直接Son.prototype = Father.prototype,所以该怎么优化呢?

我们先了解什么是原型式继承?

原型式继承

原型式继承是牛逼之父道格拉斯·克罗克福德在 2006年写了一篇文章,题为 Prototypal Inheritance in JavaScript (JavaScript 中的原型式继承)提出的。我的理解就是最纯粹的原型继承!

function object(o){
   function f(){}
   f.prototype = o;
   return new f();
}

创建一个对象,将他的prototype直接指向o对象,是否跟前面个说的son的prototype直接直接father的prototype,中间做的个f对象,解决了直接赋值prototype引起的问题尴尬。这种方式解决在原型链继承中 Son.prototype = new Father();中new Father()中所产生的额外资源消耗,因为这一步仅仅是为了继承Father中的prototype。因为其中产生的空白f()函数已经是简洁到不能再简洁了。
在原型继承发生第二种增强继承

寄生继承:

function object(o) {
  function F() {};
  F.prototype = o;
  return new F();
}
function Father () {
	this.name = 'father';
}
function createSon() {
  var tempObj= object(Father);
  tempObj.age = 12;
}

let son= createSon();

有必要说明一下,开发中函数编程有一种函数编程写法就叫:函数增强(装饰器),就是将原函数增加功能的一种写法。基于原型,把增强功能写在createSon函数中!这种写法在js继承中也叫寄生继承。
结合到组合继承当中:

寄生组合继承:

   	    function Father () {
            this.name = 'father';
        }
        Father.prototype.getName  = function () {
            console.log(this.name);
            return this.name;
        };

        function Son () {
            Father.call(this); // ②
            this.age = 12;
        }

		function object(o){
		   function f(){}
		   f.prototype = o;
		   return new f();
		}

		function createSon(sonFn, fatherFn) {
			var tempObj= object(Father);
 			tempObj.height = 176;
 			tempObj.constructor = sonFn; 
		}

        let son = craeteSon(Son, Father);

这种方式弥补了组合继承中多次调用了Father()函数产生了莫须有的消耗【new Father()替换了new F();F()内部无其他操作逻辑】
这种方式也是目前最常用的继承方式。

在es6中新增了继承语法,并且增加了extends来统一实现extends实现继承。有条件的话推荐该种继承方式!!!

好了文章写到这。

目前正在处于离职->新工作的更替状态,也就是舒舒服服的度个小长假,10天!哈哈哈,实在太舒服。希望大家都不要在996的状态中迷失自我,只有有适当的业余时间,才能叫生活,才能做自己喜欢做的事情。
happy endind…

下一篇文章打算解释探究new关键字的含义,想看的留意一下!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值