Day41:混入

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

【作者】:Kyle Simpson

【本书总页码】:213

【已读页码】:157

显式混入

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

Day41:混入_第1张图片

现在 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 进行特殊化的话,就可以跳过存在性检查。不过这种方法并不好用并且效率更低,所以不如第一种方法常用:

Day41:混入_第2张图片

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

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

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

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

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

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

3. 寄生继承

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

下面是它的工作原理:

Day41:混入_第3张图片

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

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

隐式引入

Day41:混入_第4张图片

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

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

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

你可能感兴趣的:(Day41:混入)