菜单

委托模式

相关源文件

目的与范围

本文档介绍了 JavaScript 中的委托模式,这是一种不同于传统类继承的设计方法,它利用了 JavaScript 的原型特性。委托模式利用对象链接和 this 上下文共享来实现灵活的对象组合和行为共享,而无需传统继承中的僵化层级。

虽然《Classy Objects》一章对类进行了广泛的介绍,但本页将专门关注理解和实现委托作为一种利用 JavaScript 自身的原型系统和动态 this 绑定机制的设计模式。

委托与类继承的对比

与传统的基于类的继承相比,委托代表了我们思考代码组织方式的根本性转变。委托不通过类层级创建父子关系,而是强调通过上下文共享进行协作的同级对象关系。

概念上的区别

基于类的方法基于委托的方法
垂直继承层级水平对象关系
父子关系同级对象协作
分类和特化对象组合和委托
从父类继承的行为委托给链接对象以获得行为
实例继承所有类行为对象只委托特定行为
在编写时定义的固定关系可在运行时更改的灵活关系

来源:objects-classes/ch5.md254-265 objects-classes/ch5.md266-317

委托流程图

在委托模式中,同级对象通过协作共享行为,而不是通过固定的层级继承。对象会将任务委托给其他包含所需特定行为的对象。

来源:objects-classes/ch5.md268-304

委托的核心

委托通过利用两个关键的 JavaScript 机制来实现:

  1. [[Prototype]] 链:将对象链接在一起,以便可以委托属性/方法查找。
  2. 动态 this 绑定:允许方法在不同对象的上下文中运行。

这些机制实现了作者所说的“虚拟组合”——通过委托在运行时组合对象,而不是通过继承在编写时组合。

通过上下文共享实现委托

来源:objects-classes/ch5.md306-317 objects-classes/ch4.md191-222

实现方法

主要有两种实现委托的方法:

1. 显式委托

使用 .call().apply() 来显式地在对象之间共享上下文。

这种方法在一个对象(ObjectB)上调用一个方法,但使用另一个对象(ObjectA)的上下文(this)。它被认为是“显式”的,因为委托在每个调用点都可见。

来源:objects-classes/ch5.md298-299 objects-classes/ch4.md202-222

2. 隐式委托

使用 [[Prototype]] 链自动委托属性/方法查找。

这种方法依赖于 JavaScript 引擎在 ObjectA 上查找 someMethod,如果找不到,则继续在原型链上向上搜索到 ObjectB

来源:objects-classes/ch5.md300-304 objects-classes/ch2.md268-298

委托中的对象关系

在委托模式中,对象的关系与类层级中的关系不同。

这种模型强调

  • 对象作为行为存储库
  • 水平关系而非垂直层级
  • 通过委托进行组合
  • 灵活的运行时行为共享

来源:objects-classes/ch5.md323-334 objects-classes/ch5.md376-436

委托模式的实际示例

基本委托模式

多对象委托示例

以下存储库中的示例展示了多个对象如何相互委托行为。

这说明了显式委托(使用 .call())和隐式委托(使用原型链接)。

来源:objects-classes/ch5.md376-400 objects-classes/ch5.md435-501

通过委托实现虚拟组合

委托最强大的方面之一是“虚拟组合”——即能够创建看起来组合在一起但实际上存在于不同对象中的行为。

通过委托实现的虚拟组合允许

  1. 将行为组织成逻辑清晰、聚焦的对象
  2. 通过上下文共享实现运行时行为组合
  3. 在不改变对象本身的情况下灵活地重组行为

来源:objects-classes/ch5.md316-317 objects-classes/ch5.md426-436

委托模式的优势

委托模式提供了多种优势:

  1. 灵活性:对象可以在运行时动态组合。
  2. 模块化:可以将行为组织到聚焦的、单一用途的对象中。
  3. 可测试性:更易于独立测试单个行为。
  4. 动态关系:委托结构可以在运行时更改。
  5. 无需构造函数或类层级:整体方法更简单,样板代码更少。

来源:objects-classes/ch5.md241-253 objects-classes/ch5.md497-502

委托与类方法的对比

为了更清楚地理解委托模式,将两种方法实现相同功能进行比较会很有帮助。

基于类的实现

基于委托的实现

委托方法

  • 将对象视为同级,而非层级关系
  • 将行为分离到聚焦的对象中
  • 使用上下文共享进行组合
  • 完全避免类/继承的思维模型

来源:objects-classes/ch5.md267-317 objects-classes/ch3.md91-105

何时使用委托模式

当出现以下情况时,委托模式特别有用:

  1. 您希望避免深层继承层级。
  2. 您需要灵活组合行为。
  3. 您希望按行为而不是按类型来组织代码。
  4. 您需要在运行时动态更改对象关系。
  5. 您希望更直接地利用 JavaScript 的原型系统。

但是,需要认识到委托模式并不是 JavaScript 中的主流方法。作者承认,您不会发现许多大型应用程序使用这种模式,因为大多数现代 JavaScript 代码倾向于使用基于类的模式。

来源: objects-classes/ch5.md15-26 objects-classes/ch5.md582-591

委托模式与工厂和模块

委托模式介于类继承和模块/闭包模式的代码组织方法之间。以下是它们的对比:

模式基于组合方法状态/行为关系
面向类继承层级垂直继承类中紧密耦合
委托对象链接 & 上下文水平委托分离为对等对象
模块/工厂闭包 & 函数作用域显式组合封装在闭包中

来源: objects-classes/ch5.md24-28 objects-classes/ch5.md59-96

实现考量

在实现委托模式时,请考虑以下实践:

  1. 使用有意义的对象名称来描述行为,而不是类型。
  2. 保持行为对象的专注,只承担单一职责。
  3. 使用 Object.create() 来更清晰地链接原型,而不是使用 __proto__
  4. 考虑为常见的委托模式创建辅助工具。
  5. 在显式委托与隐式委托的方法上保持一致。
  6. 记录委托关系,因为它们可能不像类层级那样一目了然。

来源: objects-classes/ch5.md229-244 objects-classes/ch2.md344-362

结论

委托模式代表了一种不同的 JavaScript 代码组织思维方式——它拥抱了语言的寄生(prototypal)特性,而不是试图将其强加于类继承之上。虽然不如类继承方法那样主流,但委托模式提供了灵活性、模块化,并且能更直接地利用 JavaScript 的核心机制。

通过理解委托模式,您将获得 JavaScript 编程工具箱中又一个强大的工具,即使您在日常实践中主要使用基于类别的代码。