2020-01-06:第六章:继承的扩展(第六章结束)

3.2借用构造函数

因为原型带有的引用值问题,开发人员开始使用借用构造函数的方法(又被称为伪造对象或者经典继承)。它会在子类型构造函数的内部调用超类型的构造函数。

先来看一个例子:

通过不同方式调用Child函数,函数的执行环境不同

可以知道,在第一次调用Child()时,其据以引用的执行环境是全局环境,在第二次通过构造函数调用Child()时,其据以引用的执行环境是a实例,因此this.constructor指向了Child函数本身(constructor指向的是生成实例的函数本身)。

此时,我们实际上是在(未来将要)新创建的Child实例的环境下调用了Father的构造函数,Father.call(this)在意义上约等于a.Father()。

这样一来,就可以在新的对象上执行父函数,通过在子类作用环境中调用父类构造函数定义的friend属性,和直接在实例中定义的friend属性一致,都只存在于Child实例中而不存在于任何原型中,避免了继承所面临的引用值问题。

1.传递参数

相对于原型链而言,借用构造函数可以在字类型构造函数中向超类型(也叫父类型)构造函数传递参数。

实质上是为子类的实例设置属性

如果为了确保父类写入属性时不会覆盖子类,可以在调用父类构造函数之后再为子类设置属性。

2.借用构造函数的问题

构造函数法无法复用函数。

但是仅仅借用构造函数,无法避免构造函数模式存在的问题:一切都在构造函数中定义,函数复用便无从谈起。而且在超超类型的原型中定义方法,对子类型而言也时不可见的。子类无法继承超类的原型中定义的方法,因此这种方式在实际使用中也不经常。

3.3组合继承(必会)

组合继承指的是将原型链和借用构造函数两种方法组合,其思路是:

使用原型链实现对原型属性和方法的继承。

而通过借用构造函数来实现对实例属性的继承。

兼具二者之长

通过组合继承的方式,我们可以使得a和b两个Child的实例分别拥有自己的属性副本,同时共享一套方法。这种方式是js中最常用的继承模式(必会)

3.4原型式继承

原型式继承是基于已有的实例创建新对象的方法。

主要看例子

在object的内部,我们首先创建一个临时性的构造函数,然后将传入的实例作为临时构造函数的原型,最后返回这个临时类型的一个实例。从本质上讲,实际上是对传入的对象做了一次浅拷贝。

基于实例的引用类型

这个方法与之前介绍过的Object.create()方法原理上相同,这个方法接受两个参数:一个用作新对象原型的对象,和一个可选的,作为新对象定义额外属性的对象。在只传入一个对象时,create()方法和原型式继承完全一致

可以看到,引用类型也是共享的

使用情况:在没有必要兴师动众的创建构造函数,而只是想创建一个与当前已有对象类似的对象的话,则可以使用原型式继承,但是要记住,原型式继承也有引用值共享的问题。

3.5寄生式继承

寄生式继承与寄生构造函数和工厂模式思想非常类似:创建一个仅用于封装继承过程的函数,继承的过程通过继承构造函数实现,并在函数的内部增强对象,最后再将对象返回,即便继承的过程是完全通过继承构造函数实现的。

可以发现与继承构造函数方法唯一的区别就是把增强对象的过程封装在了函数内

因此我们可以将寄生式继承理解为继承构造函数方法的扩展。

3.6寄生组合继承

上文讲过,组合继承是js中最常用的继承模式,但它也有自己的不足。它的不足在于无论任何情况下,都会调用两次超类型(父类型)的构造函数:一次是在创建子类型原型的时候,一次是在子类型构造函数内部:

调用两次父类的构造函数

虽然子类型最终会包括超类型对象的全部实例属性,但我们在子类构造函数中调用父类构造函数(第二次调用)还是不得不重写和父类有关的属性。

按照上面的例子:

第一次调用父类构造函数时,Child.prototype获得两个属性friend和parents。他们都将是子类的实例属性,只不过现在还位于原型中。

当创建实例时,我们会在子类构造函数中再次调用父类构造函数,这一次又在新实例上创建了实例属性parents和friend。

于是,新实例上的parents和friend就屏蔽了原型中的同名属性:我们现在拥有两组parents和friend属性:一组在原型上,一组在实例上。

通过寄生组合继承的方式,可以通过原型链的混成形式来继承方法:即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。我们不必为了指定子类型的原型而调用父类型的构造函数(消除第一次调用),我们所需要的只是超类型原型的副本而已。

1(原型式继承)根据父类的原型创造一个副本
2(寄生式继承)将上面得到的副本赋值给子类的原型

这种方式的本质是用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型:

3(整体全貌)

这个方法的高效体现在他只调用了一次父类构造函数。因此避免了在子类原型上创建不必要的,多余的属性。

寄生组合继承毫无疑问是引用各类型最理想的继承范式。

6.4小结

ECMA支持面向对象编程,但是不使用接口,只支持完全继承。对象可以在代码执行过程中创建和增强,因此动态性极强。常用的有如下几种创建对象的模式:

①.工厂模式(函数创建对象,函数内为对象添加属性和方法,然后返回对象)

工厂模式

②.构造函数模式(创建自定义引用,将新建的实例作为函数的环境对象)缺点为函数不共享(多个实例创建多个函数对象,而不是多个实例共享一个函数对象)

构造函数模式

③.原型模式(用构造函数的原型来指定应该共享的属性和方法),相比构造函数模式,多个实例共享同一组方法(来自原型)

原型模式

除此之外,js主要通过原型链实现继承,原型链的核心是将父类型的实例赋值给子类型的原型。这样子类型可以访问父类型的所有属性和方法。但是原型链的继承会导致多个实例共用一个原型的引用类型属性,因此不宜单独使用。

解决这个问题的方法是借用构造函数,我们在子类型构造函数内部调用父类型构造函数,这样就可以做到每个实例的属性独立。我们使用最多的就是这种组合继承的方式。这种模式的核心是 用原型链继承共享的属性和方法,再通过借用构造函数继承实例独有的属性。此外,我们还有几种扩展的继承模式:

①.原型式继承,在想要根据现有对象复制一个类似的对象时,可以使用这种方式,其本质是对已有对象的浅拷贝:

原型式继承

②.寄生式继承,与原型式继承相似,仅仅是用一个函数内部封装了原型式继承的过程,并且将对对象的增强也封装在了函数内部,最后返回。

寄生式继承

③.集寄生式继承和组合继承的优点于一身,式当前最好的,最有效率的继承方式。

寄生组合继承

你可能感兴趣的:(2020-01-06:第六章:继承的扩展(第六章结束))