闭包是一个函数在词法域外被调用时,仍可以记住并访问它的词法域。
例如
1 | function outerFun() { |
内部函数innerFun()的词法作用域是outerFun,执行outerFun()语句之后,我们将它的返回值(即innerFun())赋值给变量imp,此时imp指向了外部函数outerFun()的作用域,这个方式我们称之为闭包。
闭包能够让我们在定义词法作用域之外的地方,仍可以访问该作用域
闭包的使用
我们将通过回调函数、作用域、模块来理解闭包及其作用。
闭包和回调函数
即使我们没有深入理解过闭包,翻翻看我们的代码,闭包随处可见。比如写一个延时函数:
1 | function wait(message) { |
再比如,我们使用jQuery方式来实现绑定事件函数:
1 | function whichButton(name, selector) { |
当我们传递回调函数给上述代码片段中的timer、事件绑定函数,Ajax请求、以及其他异步任务时,就是在使用闭包了。
闭包和作用域
闭包共享同一个全局变量。
1 | for(var i = 0; i < 5; i++) { |
上述代码的结果是:每隔一秒,输出一个5,共输出5次。虽然每次循环,都会复制i,但这五次循环都在共享同一个全局变量,最后一次循环i为5,故此输出5个5。如果我们想实现每个一秒,输出一次,输出结果分别是1、2、3、4、5的话:
1 | for(let i = 1; i <= 5; i++) { |
let声明的迭代器变量,每次循环都会声明一次。复制代码到你的浏览器中运行下,多么神奇的块级作用域。
闭包和模块化
闭包的方式能够让我们以模块化的方式开发代码,通过提供公共API,达到模块化编程。例如,一个模块示例:
1 | function CoolModule() { |
在JS中,这种类型的编码称为模块。模块化编程需要满足以下两个条件:
必须有一个外部函数,并且至少被调用一次(每次调用都创建一个实例)。
函数内部至少返回一个内部函数,内部函数拥有外部函数的作用域,并且可以访问和改变函数的变量。
在ES6中,引入了class语法来支持模块化编程,可以使用import的方式来引入一个或者多个模块,使用export方式来提供公共API。这种方式定义的模块内容被看作一个闭包,类似于基于闭包的函数型模块。