理解闭包
闭包是什么,怎么理解?
闭包就是一个作用域的外部变量保持着对这个作用域的引用,这个引用就叫做闭包。
这么讲有些晦涩,看这个代码:
1 | function foo() { |
因为baz
本身是foo
作用域之外的变量,根据作用域的规则,baz
本身是不可以访问到foo
作用域内的变量的。
本来在通常的情况下,在函数执行完后,如果函数在后面不再使用的时候,会进行垃圾回收机制,把foo
函数内的作用域销毁,把不再使用到的内存释放掉。
但是正因为闭包,这个作用域没有被销毁。原因是foo()
函数执行之后的返回值bar
,就是bar
内部的函数即function bar(){console.log( a );}
,把这个赋值给了baz
,baz
在被调用时(baz()
)因为baz
中用到了变量a
,而变量a
是在foo
的作用域中,所以baz
必须得拥有foo
函数的作用域闭包才能够正常运行,所以foo
的作用域不会被销毁会一直存在,以便baz
之后调用的时候能正常运行。
这么说还是很绕,简单讲就是foo
函数外面的变量baz
要用到foo
作用域里面的东西,这就叫baz
拥有foo
的闭包。
再简单讲就是函数调函数。(一个同事说的,一想好像有一些道理)
在定时器、时间监听器、Ajax请求、跨窗口通信、或者任何其他的异步(或者同步)任务中,只要是用了回调函数,实际上就是在使用闭包。
循环和闭包
看一个循环的例子:
1 | for (var i=1; i<=5; i++) { |
本来这段代码的预期是,分别输出1~5,每秒一次,每次一个。但是实际上他会每秒一个的频率输出五次6。
造成这样的原因,书上讲的是循环中的五个函数是在各个迭代中分别定义的,但是他们都被封闭在一个共享的全局作用域当中,实际只有一个i
。
解决办法是运用IIFE,并在每次循环中的IIFE内保存i
的值。
1 | for (var i=1; i<=5; i++) { |
再改进一下代码:
1 | for (var i=1; i<=5; i++) { |
如果运用ES6就会更加简单,只要运用let
声明的块作用域:
1 | for (let i=1; i<=5; i++) { |
模块
模块也是利用闭包来实现的。
1 | function CoolModule() { |
这样的模式就是最常见的模块的实现方法。返回值是一个包含内部的函数的对象。因为CoolModule()
是一个函数,必须调用这个外部的函数后才能创建一个包含内部作用域的闭包。并且返回对象含有的是内部函数而不是内部变量的引用,内部变量是隐藏且私有的状态。
模块也是函数,也可以接受参数:
1 | function CoolModule(id) { |
ES6为模块添加了语法的支持,ES6可以把文件当做模块来加载,但是要注意ES6的模块没有行内格式,就是每个模块必须在一个单独的文件中。
bar.js
1 | function hello(who) { |
foo.js
1 | //仅从bar模块导入hello() |
baz.js
1 | //导入完整的foo和bar模块 |
import
可以将一个模块中的一个或多个API引入到当前作用域,并分别绑定在一个变量上。module
会将整个模块的API引入并绑定到一个变量上。export
会将当前模块的变量或函数导出为公共的API。