你以为你真的理解 Closure 吗
闭包(closure),作为前端面试中老生常谈的话题,经久不衰。今天我们就一起来深入理解一下闭包吧!
要理解闭包,首先得理解作用域链。那我们就从作用域开始咯。
作用域(scope)
作用域就是变量或者函数的可访问范围。JavaScript 中有全局作用域、函数作用域以及 ES6 中增加的块级作用域。我们看一下下面的代码:
相信你已经有自己的答案了,这里打印的是 'global'
;当执行到 bar 函数内部的时候,调用栈的状态如下图:
variable enviroment:变量环境,当声明变量时使用。var
声明的变量(能穿透 if
、for
语句)会被存入变量环境。
lexical enviroment:词法环境,当获取变量或者 this
值时使用。let
、const
声明的变量会被存入词法环境。函数内部声明的变量与函数内部块声明的变量,存放在不同的内存,可以理解为词法环境也是一个栈型结构。为了方便理解,请参照下面的代码:
fn 执行时,调用栈情况如下图:
好了,回到我们上面的问题,不知道你有没有疑惑过,bar 函数是在 foo 函数里面调用的,为什么不是打印 'foo'
呢?其实,问题的关键就在于,执行到 bar 函数内部时,是找全局作用域的 name
,还是 foo 函数作用域的 name
。那要解释清楚这个问题,首先要理解作用域链。
作用域链
每个执行上下文的变量环境中,都包含一个外部引用,指向外部的执行上下文,我们把这个外部引用称为 outer。
当 JavaScript 引擎需要使用一个变量时,会首先在当前环境(即当前执行上下文)去找,如果找不到,会去找 outer 指向的外部执行上下文。当然,如果找到 global 也找不到,那就是 undefined
了。为了方便理解,请参照下图:
至此,我们可以给作用域链下一个定义了:
JavaScript 引擎通过 outer 查找变量的这个链条就被称为 作用域链。
看到这里,你是否会有第二个疑问呢?bar 函数是在 foo 函数内部调用的,为什么 bar 函数的 outer 指向的是全局执行上下文,而不是 foo 函数执行上下文呢?这里就涉及到 词法作用域 的知识了。在 JavaScript 执行过程中,其作用域链是由词法作用域决定的。下面,就来了解一下词法作用域。
词法作用域
词法作用域 就是指作用域是由代码中函数声明的位置来决定的,所以词法作用域是静态的作用域,通过它就能够预测代码在执行过程中如何查找标识符。
那一开始那段代码,它的词法作用域链就是这样的:
foo 函数作用域 -> 全局作用域
bar 函数作用域 -> 全局作用域
根据词法作用域,bar 函数,foo 函数的上级作用域都是全局作用域。所以,当访问 bar 函数内部没有的变量时,就会去全局作用域查找。这也就解释了为什么 bar 函数的 outer 指向的是全局执行上下文而不是 foo 函数执行上下文。
最后,词法作用域是在词法分析阶段就已经确定了,与函数调用没有什么关系。换言之,词法作用域只关心变量、函数声明定义的位置。而动态作用域才关心函数是何处调用的,即函数调用是由运行时(runtime)确定的。
有了这些前置知识之后,我们再来聊一聊 闭包。
闭包(closure)
闭包在 JavaScript 中可以说是史诗级的存在,但在 JavaScript 标准 中却找不到有关闭包的定义。这个事情也是很神奇。让我们结合下面这段代码来理解闭包吧!
想必聪明的你已经知道答案了,这里会打印 'foo'
;不知你是否会有同样的疑问,foo 函数执行完成后就被销毁了呀,那么,是怎么访问到 foo 函数内部的 name 的呢?那我们一起来分析一下调用栈的情况:
当 foo 函数执行完后,bar 函数返回到外部被 f 函数保存,f 函数执行时调用栈的情况如下:
JavaScript 中,根据词法作用域,内部函数总是可以访问外部函数的变量的。当 bar 函数被返回到外部时,即使内部函数已经执行完毕,但内部函数引用外部函数的变量仍然保存在内存中,我们就将这些变量的集合称之为闭包。我们可以在 Chrome devtools 看到闭包的情况,你也可以自己动手去尝试一下。
好了,弄清楚闭包是如何产生的之后,那闭包有哪些用途呢?
可以访问内部函数的变量
让变量始终保存在内存
闭包使用不当会导致内存泄漏,所以,在实际开发中正确的使用闭包尤为重要。在使用闭包时,应该时刻记住,如果该闭包不是一直使用,且占用内存又比较大,那么应该设计成局部变量持有的闭包。这样, 在函数执行完毕销毁后,JavaScript 引擎会在下次垃圾回收时判断闭包是否已经不再使用,JavaScript 引擎就会回收这块内存。
好了,介绍到这里,想必你对闭包已经有更深刻的理解了。如果觉得有帮助,或者想帮助到更多的人,欢迎点赞分享~
参考
极客时间 · 浏览器工作原理与实践专栏
极客时间 · 重学前端专栏
winter · 如何写技术文章方法论
版权声明: 本文为 InfoQ 作者【大导演】的原创文章。
原文链接:【http://xie.infoq.cn/article/ad4e31f7153fb606f020bf4df】。文章转载请联系作者。
评论 (1 条评论)