今天看《你不知道的JavaScript》中模块化这一章,作者由闭包的特性引申到现代模块加载器的核心原理。为加深理解,小小的记录一下吧。
作用域和闭包
作用域和闭包联手,可以制造出极大的威力。模块,就是其中的一种:
1 | function foo() { |
正如在这段代码中所看到的,这里并没有明显的闭包,只有两个私有数据变量something和another,以及doSomething() 和doAnother() 两个内部函数,它们的词法作用域(而这就是闭包)也就是foo() 的内部作用域。
现在我们来做一些修改:
1 | function CoolModule() { |
很明显,foo就是一个模块的实例,而return
的对象就是这个模块暴露出来的对象。但是这里有一个让人不爽的地方就是,每次生成一个模块都需要执行一次CoolModule
这个函数。
加个IIFE试试
我们通过IIFE(立即执行函数)将这个模块改造一下:
1 | var foo = (function CoolModule() { |
是不是感觉顺眼多了?利用了IIFE的特性形成了一个很常见的单例模式,使我们想暴露出来的公共API可以很方便的调用和修改模块内部的函数和变量。
实现一个模块加载器
大多数模块依赖加载器/ 管理器本质上都是将这种模块定义封装进一个友好的API。核心代码如下:
1 | var MyModules = (function Manager() { |
这段代码的核心是modules[name] = impl.apply(impl, deps)
。为了模块的定义引入了包装函数(可以传入任何依赖),并且将返回值,也就是模块的API,储存在一个根据名字来管理的模块列表中。下面展示了如何使用它来定义模块:
1 | MyModules.define( "bar", [], function() { |
由于闭包的特性,modules这个对象永远不会销毁,模块就可以达到垒增的效果。到目前为止,一个模块加载器就完成啦!