1.模块(Model)

通常所指“模块”是指编程语言所提供的代码组织机制,利用此机制可将程序拆解为独立且通用的代码单元。

根据不同的关注点,将一个项目的可以共享的部分抽取出来,形成独立的Module,就是模块化。

对于JavaScript来说,在ES6之前,并没有语言内置的模块机制,但我们用一些方式自制了某种模块机制,像CommonJS / AMD甚至建立了普遍接受的社区标准。虽然它们都是模块机制,但会有一些重大或微妙的差异。故当我们提到JS模块时,如果没有足够的上下文,有时需要明确是CommonJS module或AMD module或ES6 module。

对于CSS来说,并没有普遍接受的“CSS模块”概念。一个CSS样式表里可以通过@import来引入其他样式表,但我们通常并不称之为“模块”。多份样式表以cascade机制结合,这和我们一般编程语言中模块互相调用的方式相当不同。且CSS的@import语义基本上就是最简单的include,也就是将@import语句替换为导入样式表的内容。而编程语言中的导入模块会在当前作用域导入命名空间、符号等,比简单的include要复杂许多。

有关“CSS模块”的问题,我们后面还会讨论。

注:在Web标准中,“CSS module”其实指CSS spec本身的模块化。这也是我们应该避免采用“CSS模块”来指代CSS代码的组织结构的重要原因。

其实我公司里对“模块”的用法也比较随便。比如我们有/static/js/modules/目录,其实下面就是一些脚本,并没有采用任何一种module规范。再如我们有/src/modules/目录,下面每个子目录是业务模块,里面包含了view、controller和相关的各种类。

这里一个是历史因素——目录结构不是我建立的,大家习惯如此,都知道我们讲的“module”是指业务模块,跟具体编程语言里的module没有直接的关系,只要沟通没有什么障碍,那也不必改了。不过当我们完成引入JS module loader和相关设施之后,很可能还是需要重新调整文档和目录命名,以避免可能的理解错位。

回到关于“模块”的定义讨论上,我建议运用此术语时尽量避免扩张性解释——即避免在脱离特定机制的general的“模块化”的意义上使用“模块”这个词。

比如,传统的JS代码组织方法之一,是挂在global上的层级命名空间。此严格上不好称之为“模块”。原因是namespace只提供逻辑划分,不解决代码本身的划分。如果没有其他机制,代码划分仍然是文件为单位,并由开发者自己指定script加载。同理,我们通常认为C++里没有模块(尽管有namespace和include),但是PHP我们认为有模块(因为它有autoloader可以根据namespace映射到目录去加载文件)。

当然,即便编程语言没有模块,我们仍然可以通过一些方式进行“模块化”编程,但这种模糊的用法有可能造成误解。在JS这边因为我们已经有很成熟的CommonJS / AMD / ES6 module了,更应避免模糊用法。

2.组建(component)

另一个概念是“组件”。大体上“组件”和“模块”的概念是类似的,只是“组件”通常指更high-level的东西。

我个人体会,“模块”指代码单元,其意义偏向静态的代码结构。而“组件”指功能单元,其意义偏向运行时的结构,并有更复杂的控制(如组件实例的生命周期管理)。

举例来说,在组件系统中,你应该可以比较容易的做到在运行时查找某种组件并替换为另一种组件(热插拔)。而这通常并不作为模块系统的需求——即使模块系统支持动态加载,通常也不支持注销旧模块;即使支持注销旧模块,通常也不支持替换所有旧模块的引用(意味着需要重新 实例化/初始化 模块依赖树上所有直接或间接引用此模块的模块)。

注:的确有某Node.js平台下的游戏框架设计以class作为模块单元,通过替换prototype来做到模块的热插拔。不过这其实要求非常多的编程方式约定,实际上可被视为使用的是JS的一个裁剪的特性子集,因而不具有普遍性。