多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
什么是闭包?可以在自己定义的词法作用域以外的地方执行,就是闭包。 ``` function foo(){ var a = 2; function bar(){ console.log(a); } return bar; } var baz = foo(); baz(); //2 ``` bar很明显被执行了,但是它在自己定义的词法作用域以外的地方执行了。 foo()执行后,期待foo()的整个内部作用域都被销毁,看起来foo()的内容不会再使用,应当被回收。但是,闭包的神奇之处就在于可以阻止这件事情发生。事实上内部作用域依然存在,所以无法被回收。bar声明的位置导致bar拥有涵盖整个foo内部作用域的闭包,它一直在引用着该作用域。正是因为它无法被销毁,才使得函数可以继续访问定义时的词法作用域。 闭包的常见场景就是在一个函数内的回调函数: ``` function wait(message){ setTimeout(function(){ console.log(message); }, 1000) }; wait('hello') //在1000毫秒后,匿名函数仍然能够获取到message的值,这是因为wait的内部作用域不会消失,匿名函数一直拥有对它的引用。 function setupBot(name, selector){ $(selector).click(function(){ console.log(name); }) }; setupBot('qc', DIV); //setupBot执行完后,函数并不会被回收,当你点击的时候匿名函数仍然能获得当初的name。 ``` #### 循环与闭包 ``` for(var i = 0; i < 5; i++){ setTimeout(function(){console.log(i)}, 1000); } //结果很明显是5个6,因为i是被共用的 ``` 如何解决?思路是每次循环时为i创建一个副本来保存当前i的值。立即执行函数会自动创建自己的作用域。 ``` for(var i = 0; i < 5; i++){ (function(){ setTimeout(function(){ console.log(i) }, 1000); })(); } ``` 然而这是不够的,结果依旧是5个6。立即执行函数内部的作用域是空的,需要为作用域内添加点东西: ``` for(var i = 0; i < 5; i++){ (function(j){ var j = i; setTimeout(function(){ console.log(j) }, 1000); })(); } //也可以写成 for(var i = 0; i < 5; i++){ (function(j){ setTimeout(function(){ console.log(j) }, 1000); })(i); } ``` 现在好了,每一次迭代时都会通过立即执行函数创建内部作用域保存i的值,在循环结束后,闭包仍然能够访问内部作用域的值。 #### let let拥有自动创建作用域的功能: ``` for(let i = 0; i < 5; i++){ setTimeout(function(){console.log(i)}, 1000); } ``` 问题解决! #### 模块 ``` function module(){ var something = 'cool'; var another = [1, 2, 3]; function doSomething(){ console.log(something); } function doAnother(){ console.log(another); } return { doSomething: doSomething, doAnother: doAnother } } var foo = module(); foo.doSomething(); foo.doAnother(); //函数必须执行,否则无法创建内部作用域和闭包 //该函数返回的是含有对内部函数引用的对象,而不是对内部数据变量引用的对象。 ``` 模块模式需要具备两个必要条件 1. 必须有外部的封闭函数,该函数必须至少被调用一次(每调用一次都会创建一个新的模块)。 2. 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态。 由此可以看出来,一个拥有函数属性的对象并不是真正的模块。 由于module每执行一次都会创建新的模块实例,当只需要一个实例的时候,可以使用立即执行函数来实现单例模式: ``` var foo = (function module(){ var something = 'cool'; var another = [1, 2, 3]; function doSomething(){ console.log(something); } function doAnother(){ console.log(another); } return { doSomething: doSomething, doAnother: doAnother } })() foo.doSomething(); ``` 模块的另一个强大的用法是可以修改模块内的任意东西: ``` var foo = (function(id){ function change(){ api.identify = identify2; } function identify1(){ console.log(id); } function identify2(){ console.log(id.toUpperCase()); } var api = { change: change, identify: identify1 } return api; })('foo'); foo.identify(); //foo foo.change(); foo.identify(); //FOO ``` 由于闭包的存在,整个模块的作用域会一直存在着,api这个变量永远不会销毁。当调用change后,api的identify就由1替换成了2。 #### 现代模块机制 请认真思考下面的代码原理 ``` var Module = (function(){ 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}; })() ``` ``` Module.define("bar", [], function(){ function hello(name){ return name; } return {hello: hello}; }) Module.define("foo", ["bar"], function(bar){ function hi(){ console.log('name is: ' + bar.hello('qc')); } return {hi: hi}; }) ``` ``` var bar = Module.get("bar"); var foo = Module.get("foo"); console.log(bar.hello('mescal')); // mescal foo.hi(); // name is: qc ``` 这种模块机制是运行时执行的,它的api只有在运行时才被创建,我们可以在运行时修改api,参考identify那个例子。而ES6的模块机制是基于文件的,它的api就是静态的,因此错误检查在编译阶段就执行了,而上述代码的模块机制的错误检查在运行时才开始。