书籍简介
《Mastering Modular JavaScript》,是 O'Reilly 的早期的一本书。它是探索 JavaScript 架构的五本丛书系列之一。本书探讨了软件复杂性的基本原理,以及如何应用这些概念在 JavaScript 中以获得可维护性和可读性的模块化应用程序。这本书还附带了许多简明直接的建议和实例。你可以在 ponyfoo.com/books 在线免费阅读它。
作者简介
Mastering Modular Javascript 的作者为 NicolásBevacqua ,是 Elastic 的高级软件工程师,可以在 Twitter 上搜索 @nzgb 来关注他, 他的博客地址为 https://ponyfoo.com/ 。
NicolásBevacqua 曾写过《JavaScript Application Design 》这本书,中文译本为Javascript Web 应用开发,另外一本是《Practical Modern JavaScript》,而《Mastering Modular Javascript》 是作者的第三本书。
引言
本书中的大部分建议、思考和指导都不是 JavaScript 特有的,因此本书不仅限于使用JavaScript 和 ES6 的开发者和爱好者。只要对编写可读、可维护和可扩展模块化的代码感兴趣的开发者都可以阅读这本书。
本书目的是为大家讲解如何编写模块化代码。通过阐述模块化的体系结构背后的基础知识和JavaScript 模块化的演进历程,来让大家更好地理解编写模块化应用程序并从中有所收获。
书中没有冗长的、多页的和分析透彻的各种例子,大部分是精炼短小的代码段,更多的是希望将本书学到的东西运用到你自己的程序中解决一些问题,并能够在几种可能的实现方法中权衡利弊选择最适合的方式去应用它。
目录
第一章:模块化思维的介绍
- 模块化思维介绍
- 模块化简史
- 模块化设计好处
- 模块化粒度
- 模块化 JavaScript 的必要性
第二章:模块化原则
- 模块化设计要点
- CRUST 原则
第三章:模块化设计
- 持续发展一个模块
- CRUST 的思考
第四章:内部结构
- 内部复杂性
- 重构复杂的代码
- 作为熵的状态
- 数据结构为王
第五章:模块化模式与实践
- 利用现代 JavaScript
- 组合与继承
- 代码模式
第六章:开发的方法与哲学
- 安全配置管理
- 显式依赖管理
- 作为黑盒子的接口
- 构建、发布、运行
- 开发与生产的平等性
- 抽象问题
第一章
第一章讨论在 JavaScript 环境中模块化的演变历史,从早期将 JavaScript 嵌入onclick
属性,到 CommonJS,最后是原生 ECMAScript 模块。

然后研究了编写模块化代码的好处,以及在系统的服务、应用程序、组件、模块、函数和块中都这么做的好处。 主要的好处总结为一下几点:
- 避免变量名冲突
- 增加代码库变更的能力
- 代码高度可维护
- 有利于健壮、直观的接口的实现
关于组件,作者强调了一点:组件的实现本质就是它们的接口。
作者就模块化的粒度进行了讨论,一方面指出如果一个项目需求超出初始范围,那么可以考虑将项目拆分为几个较小的项目,这样更易于较小团队的项目管理,同时若想要应用程序更易于维护,将代码划分为水平的多个层级,每个层级在划分多个组件,并由更小的组件作为连接的纽带,使这些组件组合在一起。
另一方面,在设计组件化应用的同时,不要过早的进行规划,过早的思考会浪费我们大量的时间,并卡在难以想象的抽象化问题中,短期来说并没有可以预见的受益。我们应专注于解决当下或即将遇到的问题,而不是去预估一个可能的未来。顺其自然,在对的时间做对的事情,让我们的应用自然的发展,正确的时间对其进行抽象,取其精华,去其糟粕。
第二章
本章介绍了模块化设计的基本内容。研究了如何编写对 API 接口友好的模块,该模块如何使用,职责是什么以及接口上应该有哪些内容。
作者从模块化可以减少程序复杂性的角度,引申出“复杂性”这个概念,并对复杂性这个词在字典里的定义进行分析,总结了在程序代码中的复杂性为:一个标准程序中任何内容都可能是很复杂的,用代码行来度量复杂性是行不通的,复杂性不仅是实现复杂,接口也可能很复杂。
作者阐述了如何帮助我们在组件的级别上管理复杂性。先是介绍了如何遵循 SRP(单一职责原则)去设计 API。接着提出了 API 优先的原则和揭示模式(revealing pattern)的思想,并用一个简明的示例来说明了 API 优先原则的重要性。最后总结出:开发可维护的组件的首要任务是专注于实现公共 API ,一个功能模块的好坏不取决与它的开发实现,而是取决于它的公共接口。
最后介绍了如何寻找正确的抽象,减少抽象中涉及到的冲突,保持API层面的一致性。作者指出,一个好的API要遵循 CRUST 原则:
- 一致(Consistent)
- 弹性(Resilient)
- 明确(Unambiguous)
- 简单(Simple)
- 小巧(Tiny)
同时作者对每项特性拆分成子章节进行了详细的说明。
第三章
第三章的很大一部分致力于帮助读者在关注模块化及其接口可扩展的同时,理解并解决各种问题,并在等待明确的模式出现之前避免抽象。
作者重申这个观点:抽象应该随其自然,而不要强迫着去实现,当抽象的好处显而易见时,再去抽象它。作者用 HTTP 库举例,介绍它是如何根据用户所需要的用例去做正确的抽象的。同时,当我们不确定对外暴露哪些功能时,不要对外暴露任何接口,因为我们的接口只为特定的用例提供特定的解决方案。如果我们对此提供一堆解决方案,会使用户感到困惑,不知道哪种方式是最好的。
接着遵循第二章的 CRUST 原则保持组件的简单性,简单概括有以下几个方面:
- 作者阐述了如何帮助我们在组件的级别上管理复杂性。先是介绍了如何遵循 SRP(单一职责原则)去设计 API 。接着提出了API 优先的原则和揭示模式(revealing pattern)的思想,并用一个简明的示例来说明了 API 优先原则的重要性。最后总结出:开发可维护的组件的首要任务是专注于实现公共 API ,一个功能模块的好坏不取决与它的开发实现,而是取决于它的公共接口。
- 特性隔离,即使并没有在其实组件中重用,也要将一个功能实现分解为几个较小的组件,来分解组件内部的复杂性
- 做好内部设计权衡,第一步找到确认完善的需求和对应的接口,接着保持实现的结果与接口文档一致,实现尽可能简单,最后,内部构件尽可能高性能
第四章
本章的大部分篇幅是在讨论通过重构代码来降低复杂度的方法。然后讨论了状态在复杂性方面的作用以及如何减轻复杂性,如何选择正确的数据结构控制复杂性。
作者指出嵌套是类似于「回调地狱」或者「Promise地狱」这类模式的复杂性的源头,这些复杂性存在于这些代码的衔接之处,一旦我们想要去修复某些问题,就要完全地理解程序的上下文逻辑。同时作者也给出降低嵌套复杂度的解决方式:将逻辑扁平化,这样流程代码就能从其他代码中剥离出来,然后尝试将业务代码划分为独立的组件。
在开发一个功能时,应提前做好设计和划分好关注点,减少开发中产生紧耦合的风险。引入外部框架时,我们可以采用它的约定和实现,提高我们代码的一致性。但是当我们所选择的框架在不支持我们的某个需求时,我们可以创建一个单独的层,开发我们需要的组件。这种清晰定义的层级编写高效可维护的应用至关重要。
大型应用的整体看起来很复杂,但是如果我们将应用分解为更小的组件之后,整体的复杂性虽然增加了,但是局部的复杂性却减少了,如此一来,不需要完全理解这个应用,我们也可以很容易的维护大型应用其中的独立部分。
在处理数据结构时,一方面要将数据与逻辑分离,另一方面,处理特定数据结构的代码尽可能放在小模块中。当系统功能井喷时,要将逻辑拆分到同一个目录的不同文件中。
第五章
第五章介绍了在 Javascript 中如何利用现代语言结构来编写清晰的程序。同时研究了继承和组合等模式,并讨论了每种模式的使用场景。通过对经典模式的梳理,例如揭示模块(revealing modules)、对象工厂、事件触发和 JSON 消息传递,来探讨模块化设计。
作者指出使用风格一致的代码可以控制代码库的复杂度,而到底使用哪种风格并不重要,重要的是我们能始终如一的强制执行它。
对于 ES6 的一些新特性的使用,例如解构,rest 参数和扩展运算符等做了详细的讨论分析。同时介绍了回调函数、 promise 和异步函数优缺点,总结出结论:将异步流分解成树状模型,会使代码更加轻量化,再用额外一些函数将这些树链接起来,可以明显的降低复杂性。
第六章
第六章描述了一个熟练的模块开发者的思维模式。同时包含了解决安全问题和依赖管理、构建和集成过程、接口和抽象,以及模块设计建议和最佳实践。
总结
掌握模块化 JavaScript 并不是严格遵循一套定义明确的规则,而是能够从用户的角度出发,为可能即将到来的功能开发预先做好规划。接口文档和接口设计同样重要,而内部的实现细节可以在后续的开发中不断进行改进,最终让模块化产生真正的价值。
笔者通读了这本书,简单总结了每章的要点,在本书第五章和第六章中,作者使用了大量的非常具有代表性的代码示例来进行阐述讲解,非常值得仔细阅读揣摩,相信会对你有很大启发。对本书有兴趣的同学,可以登录作者的官网去查阅了解更多信息。此外,关于这本书的译本,饿了么大前端的小伙伴们已经开始在紧锣密鼓的翻译了,预计最快年底本书的中文版就会出版并与大家见面了。这本书很值得大家反复翻阅,相信每次都会有新的感悟,笔者在翻译这本书的过程中,学习到很多,因此在这里与大家分享这本书。