关于闭包
这篇文章是2018-06-26号创建的,那个时候我接触到了闭包,然后写下了自己的理解。2019年的时候我编辑过一次。因为自己对闭包有了新的理解。。
现在我看了《你不知道的JavaScript》这本书的上卷后,删除了之前编辑的内容。准备重新分享我对闭包的理解。
词法作用域
词法作用域,又叫静态作用域。
说道词法作用域就要提一下编译的三个阶段:
- 词法分析
- 语法分析
- 代码生成
而词法作用域就是在词法分析阶段确定的,换句话说就是你在写代码时将变量放在哪个代码块里来决定。
看这样一段代码:
1 | var a = 100 |
稍微有点JavaScript基础的同学都知道,输出为什么是20。
作用域发生嵌套时,引擎会从当前作用域往外层逐层查找,就和前端的click事件冒泡一样。
什么是闭包
我印象里的关于闭包的定义有很多:
- 一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。 —— MDN WEB Docs
- 闭包就是能够读取其他函数内部变量的函数。 —— 阮一峰的网络日志
1 | function foo(){ |
- 这段代码中,bar() 仍然持有对foo函数的作用域的引用,这个引用就叫做闭包。 —— 《你不知道的JavaScript》
这三种说法都没有问题,正常的词法作用域查找规则都是闭包的一部分,所以说,闭包无处不在。
闭包的应用
IIFE
IIFE 全称是 立即调用函数表达式(immediately-invoked function expression)
1 | var a = 2 |
首先IIFE确实创建了闭包,但是a是通过普通的词法作用域查找而非闭包被发现的。
所以,这里我也迷糊了。我暂时认知为,它创建了闭包,但是没有使用闭包!
循环
在块作用域没出来之前,当循环里面有异步操作时,如何保证循环变量的值对应的是每一次循环时的值?
1 | for(var i=1; i<=5; i++){ |
大部分同学一眼都能看出来,这段代码会以每秒一次的频率输出五个 6
那么如何按照我们的预期,每个一秒依次输出1~5呢?
1 | for (var i = 1; i <= 5; i++) { |
我们封闭settimeout的上级作用域,使i的查找的不再是var i ,而是 (function(i){})(i) 这个IIFE传递进来的,每一次循环的i值,会被保存下来,不会随着循环而改变。
可以参考以前搬运的一片文章:一道有意思的面试题
回调函数
回想一下,根据上面对闭包的定义,JavaScript上所有使用了回调函数的地方都使用了闭包!
模块
node.js的模块加载参照的是CommonJS规范,每个 .js 能独立一个环境只是因为 node 帮你在外层包了一个自执行函数
1 | (function (exports, require, module, __filename, __dirname) { |
这样的话。a.js引用b.js,并且使用其暴露出去变量和方法不就是在使用闭包吗?
最后,我也不知道我又没有表达清楚,但我对闭包确实有了更深的理解。
参考链接: