专栏名称: 钱塘风华
一湾钱塘,一片风华,一个陪你走心的文艺暖男,...
今天看啥  ›  专栏  ›  钱塘风华

Day41:混入

钱塘风华  · 简书  ·  · 2019-05-04 23:32

文章预览

【书名】:你不知道的JavaScript(上卷)

【作者】:Kyle Simpson

【本书总页码】:213

【已读页码】:157

显式混入

由于 JavaScript 不会自动实现 Vehicle到 Car 的复制行为,所以需要手动实现复制功能。这个功能在许多库和框架中被称为extend(..),但是为了方便理解我们称之为 mixin(..)。

现在 Car 中就有了一份 Vehicle 属性和函数的副本了。从技术角度来说,函数实际上没有被复制,复制的是函数引用。所以,Car 中的属性 ignition 只是从 Vehicle 中复制过来的对于 ignition() 函数的引用。相反,属性 engines 就是直接从 Vehicle 中复制了值 1。

Car 已经有了 drive 属性(函数),所以这个属性引用并没有被 mixin 重写,从而保留了Car 中定义的同名属性,实现了“子类”对“父类”属性的重写。

1. 再谈多态

显式多态:Vehicle.drive.call( this )。

相对多态:inherited:drive()。

JavaScript(在 ES6 之前)并没有相对多态的机制。所以,由于 Car 和Vehicle 中都有 drive() 函数,为了指明调用对象,必须使用绝对(而不是相对)引用。通过名称显式指定 Vehicle 对象并调用它的 drive() 函数。

但是如果直接执行 Vehicle.drive(),函数调用中的 this 会被绑定到Vehicle 对象而不是Car 对象。因此,使用 .call(this)来确保 drive() 在 Car 对象的上下文中执行。

如果函数 Car.drive() 的名称标识符并没有和 Vehicle.drive() 重叠(或者说“屏蔽”)的话,就不需要实现方法多态,因为调用mixin(..) 时会把函数 Vehicle.drive() 的引用复制到 Car 中,因此可以直接访问this.drive()。正是由于存在标识符重叠,所以必须使用更加复杂的显式伪多态方法。

在支持相对多态的面向类的语言中,Car 和 Vehicle 之间的联系只在类定义的开头被创建,从而只需要在这一个地方维护两个类的联系。

但是在 JavaScript 中(由于屏蔽)使用显式伪多态会在所有需要使用(伪)多态引用的地方创建一个函数关联,这会极大地增加维护成本。此外,由于显式伪多态可以模拟多重继承,所以它会进一步增加代码的复杂度和维护难度。

使用伪多态通常会导致代码变得更加复杂、难以阅读并且难以维护,因此应当尽量避免使用显式伪多态,因为这样做往往得不偿失。

2. 混合复制

分析一下 mixin(..) 的工作原理。它会遍历 sourceObj的属性,如果在 targetObj没有这个属性就会进行复制。由于是在目标对象初始化之后才进行复制,因此一定要小心不要覆盖目标对象的原有属性。

如果是先进行复制然后对 Car 进行特殊化的话,就可以跳过存在性检查。不过这种方法并不好用并且效率更低,所以不如第一种方法常用:

这两种方法都可以把不重叠的内容从 Vehicle 中显性复制到 Car 中。

复制操作完成后,Car 就和 Vehicle 分离了,向 Car 中添加属性不会影响 Vehicle,反之亦然。

由于两个对象引用的是同一个函数,因此这种复制(或者说混入)实际上并不能完全模拟面向类的语言中的复制。

JavaScript 中的函数无法(用标准、可靠的方法)真正地复制,所以只能复制对共享函数对象的引用。如果修改了共享的函数对象(比如ignition()),比如添加了一个属性,那 Vehicle 和 Car 都会受到影响。

如果你目标对象中显式混入超过一个对象,就可以部分模仿多重继承行为,但是仍没有直接的方式来处理函数和属性的同名问题。

一定要注意,只在能够提高代码可读性的前提下使用显式混入,避免使用增加代码理解难度或者让对象关系更加复杂的模式。

3. 寄生继承

显式混入模式的一种变体被称为“寄生继承”,它既是显式的又是隐式的,主要推广者是Douglas Crockford。

下面是它的工作原理:

调用 new Car() 时会创建一个新对象并绑定到 Car 的 this 上。但是因为没有使用这个对象而是返回了我们自己的 car 对象,所以最初被创建的这个对象会被丢弃,因此可以不使用 new 关键字调用 Car()。

这样做得到的结果是一样的,但是可以避免创建并丢弃多余的对象。

隐式引入

通过在构造函数调用或者方法调用中使用 Something.cool.call( this ),实际上“借用”了函数 Something.cool() 并在 Another 的上下文中调用了它。最终的结果是 Something.cool() 中的赋值操作都会应用在 Another 对象上而不是Something 对象上。

因此,把 Something 的行为“混入”到了 Another 中。

虽然这类技术利用了 this 的重新绑定功能,但是 Something.cool.call( this ) 仍然无法变成相对(而且更灵活的)引用,所以使用时千万要小心。通常来说,尽量避免使用这样的结构,以保证代码的整洁和可维护性。

………………………………

原文地址:访问原文地址
快照地址: 访问文章快照
总结与预览地址:访问总结与预览