当函数能够记住并访问所在的词法做用域时, 就产生了闭包, 即便函数是在当前词法做用域以外执行。javascript
function wait(message) { setTimeout( function timer() { console.log( message ); }, 1000 ); } wait( "Hello, closure!" );
将一个内部函数( 名为 timer) 传递给 setTimeout(..)。 timer 具备涵盖 wait(..) 做用域的闭包, 所以还保有对变量 message 的引用。wait(..) 执行 1000 毫秒后, 它的内部做用域并不会消失, timer 函数依然保有 wait(..)做用域的闭包。
深刻到引擎的内部原理中, 内置的工具函数 setTimeout(..) 持有对一个参数的引用, 这个参数也许叫做 fn 或者 func, 或者其余相似的名字。 引擎会调用这个函数, 在例子中就是内部的 timer 函数, 而词法做用域在这个过程当中保持完整。这就是闭包。java
在定时器、 事件监听器、Ajax 请求、 跨窗口通讯、 Web Workers 或者任何其余的异步(或者同步) 任务中, 只要使用了回调函数, 实际上就是在使用闭包!闭包
还有一种代码模式利用了闭包——模块app
function CoolModule() { var something = "cool"; var another = [1, 2, 3]; function doSomething() { console.log( something ); } function doAnother() { console.log( another.join( " ! " ) ); } return { doSomething: doSomething, doAnother: doAnother }; } var foo = CoolModule(); foo.doSomething(); // cool foo.doAnother(); // 1 ! 2 ! 3
首先, CoolModule() 只是一个函数, 必需要经过调用它来建立一个模块实例。 若是不执行外部函数, 内部做用域和闭包都没法被建立。
其次, CoolModule() 返回一个用对象字面量语法 { key: value, ... } 来表示的对象。 这个返回的对象中含有对内部函数而不是内部数据变量的引用。 咱们保持内部数据变量是隐藏且私有的状态。 能够将这个对象类型的返回值看做本质上是模块的公共 API。
这个对象类型的返回值最终被赋值给外部的变量 foo, 而后就能够经过它来访问 API 中的属性方法, 好比 foo.doSomething()。异步
所以模块模式须要具有两个必要条件:ide
模块模式另外一个简单但强大的变化用法是, 命名将要做为公共 API 返回的对象:函数
var foo = (function CoolModule(id) { function change() { // 修改公共 API publicAPI.identify = identify2; } function identify1() { console.log( id ); } function identify2() { console.log( id.toUpperCase() ); } var publicAPI = { change: change, identify: identify1 }; return publicAPI; })( "foo module" ); foo.identify(); // foo module foo.change(); foo.identify(); // FOO MODULE
经过在模块实例的内部保留对公共 API 对象的内部引用, 能够从内部对模块实例进行修改, 包括添加或删除方法和属性, 以及修改它们的值。工具
大多数模块依赖加载器 / 管理器本质上都是将这种模块定义封装进一个友好的 API。code
var MyModules = (function Manager() { var modules = {}; function define(name, deps, impl) { for (var i = 0; i < deps.length; i++) { deps[i] = modules[deps[i]]; } modules[name] = impl.apply(impl, deps); } function get(name) { return modules[name]; } return {define: define, get: get}; })();
这段代码的核心是 modules[name] = impl.apply(impl, deps)。 为了模块的定义引入了包装函数(能够传入任何依赖), 而且将返回值, 也就是模块的 API, 储存在一个根据名字来管理的模块列表中。对象
下面展现了如何用它来定义模块:
MyModules.define("bar", [], function () { function hello(who) { return "Let me introduce: " + who; } return {hello: hello}; }); MyModules.define("foo", ["bar"], function (bar) { var hungry = "hippo"; function awesome() { console.log(bar.hello(hungry).toUpperCase()); } return {awesome: awesome}; }); var bar = MyModules.get("bar"); var foo = MyModules.get("foo"); console.log(bar.hello("hippo")); // Let me introduce: hippo foo.awesome(); // LET ME INTRODUCE: HIPPO
"foo" 和 "bar" 模块都是经过一个返回公共 API 的函数来定义的。 "foo" 甚至接受 "bar" 的示例做为依赖参数, 并能相应地使用它。