模块系统及其模式
正如我们所说,模块是构建非复杂应用程序的砖块,也是通过将所有未明确标记为导出的函数和变量保持私有来实现信息隐藏的主要机制。
在了解 CommonJS 的具体细节之前,我们先来讨论一种有助于信息隐藏的通用模式,我们将使用它来构建一个简单的模块系统,这就是揭示模块模式(RMP)。
揭示模块模式
浏览器中 JavaScript 的一大问题是缺乏命名间隔。每个脚本都在全局范围内运行;因此,内部应用程序代码或第三方依赖项可能会污染该范围,同时暴露自己的功能片段。这可能会造成极大的危害。例如,设想一个第三方库实例化了一个名为 utils 的全局变量。如果任何其他库或应用程序代码本身不小心覆盖或更改了 utils,那么依赖它的代码很可能会以某种不可预知的方式崩溃。如果其他库或应用程序代码意外调用了另一个库中仅供内部使用的函数,也会产生不可预知的副作用。
总之,依赖全局作用域是一项风险很大的工作,尤其是随着应用程序的增长,您不得不越来越多地依赖其他个体实现的功能。
解决这类问题的一种流行技术叫做 "揭示模块模式",它看起来像这样:
const myModule = (() => {
const privateFoo = () => {}
const privateBar = []
const exported = {
publicFoo: () => {},
publicBar: () => {}
}
return exported
})() // once the parenthesis here are parsed, the function
// will be invoked
console.log(myModule)
console.log(myModule.privateFoo, myModule.privateBar)
这种模式利用了自调用函数。这种类型的函数有时也被称为 "立即调用的函数表达式"(IIFE),用于创建一个私有的作用域,只输出公共的部分。
在 JavaScript 中,函数内部创建的变量无法从外部作用域(函数外部)访问。函数可以使用 return 语句有选择性地向外层作用域传播信息。
这种模式本质上就是利用这些属性来隐藏私有信息,只输出面向公众的 API。
在前面的代码中,myModule 变量只包含导出的 API,而模块的其他内容从外部几乎无法访问。
日志语句将打印如下内容:
{ publicFoo: [Function: publicFoo],
publicBar: [Function: publicBar] }
undefined undefined
这表明只有导出的属性可以从 myModule 直接访问。
正如我们稍后将看到的,该模式背后的思想被用作 CommonJS 模块系统的基础。