所有 JavaScript 函数都是闭包吗?
是的
这大概是最让人头疼的话题之一了。很多读者可能根本不会往下看,但是别担心!坚持下去,我们会尽量用最简单的方式解释。为了解释什么是闭包,我不会从那个常见的、无处不在的函数嵌套的例子开始(不过之后会提到)。闭包在实现事件处理程序(DOM Level 2)时也扮演着非常重要的角色。我们来看一个例子吧?
在 CodePen 上试试看
上面的代码很简单,就是一个接收整数参数的函数,它为两个不同的按钮设置事件监听器。第一个按钮被点击时,事件监听器会调用一个回调函数。回调函数会递增计数器,并在弹出的提示框中显示结果。第二个事件监听器除了回调函数会递减同一个计数器之外,没有其他作用。我们是否理解,即使`setClickHandler`函数已经执行完毕(所有变量和参数都已从执行栈中移除),这两个事件监听器仍然会监听各自按钮的点击事件?没错,事件监听器的工作原理就是这样。你只需设置一次,它们就会一直存在,除非你调用节点的`removeEventListener` 方法。这段代码片段中还有另一个值得注意的地方。当函数“setClickHandler”已经离开调用栈(内存中不再有任何参数和局部变量)时,为什么回调函数在尝试访问参数“counter”(实际上它位于“setClickHandler”的词法作用域内)时不会抛出“ReferenceError”?如果你想把这归因于作用域链,你的说法也并非完全错误,但接下来我会问,为什么作用域链中位于上游的某些内容在所有者函数离开执行栈后仍然在内存中保持活动状态?这时你就需要提到闭包了。
什么是闭环?
让我们来理清一下之前讨论的内容。MDN指出,一个函数及其对周围作用域的引用构成了该函数的闭包。那么,你难道不会质疑,JavaScript 中每个函数默认情况下都能访问其词法作用域(外部作用域)内的所有内容吗?如果真是这样,那么每个函数在创建之初就必须拥有一个闭包。Bingo!猜猜谁又说对了?例外情况是使用 `Function` 构造函数创建的函数。我们通常倾向于忽略函数在其词法作用域内执行完毕后形成闭包的能力。如果同一个函数从其词法作用域返回,并在之后被其他地方引用呢?该函数仍然能够访问其词法作用域中的引用(变量和函数的内存位置),就像它没有被返回一样。等等!怎么可能?外部函数执行完毕时,作用域链不是应该终止了吗?事实上,它确实终止了!但在内部函数被返回之前,也就是函数终止之前,作用域链会被保存为内部函数自身的一个属性(或者用 ECMAScript 的说法,是一个内部槽)。规范中称之为`[[Environent]]`,但谷歌 Chrome 使用的是类似 ` ([[Scopes]]: { closure } )` 的形式。看来 Chrome 是沿用了2011 年的旧规范才这么做的。值得一提的是,Firefox 和 IE 甚至不像 Chrome 那样显示函数的内部槽。好了,所以这个保存的作用域链其实就是闭包。这就是内部函数如何对其外部作用域中的引用进行闭包的方式。
如果你尝试在开发者工具中检查这类函数的执行过程,你会注意到,一旦该函数被压入执行栈的顶部,它的闭包就会出现在作用域链中,就好像该函数一直带着它一样。如果你只有五岁,闭包就像一个在学校里学到的东西之外,还在学校里做笔记([[Environent]])的函数。
检测到闭合
如果你真的想把这个可视化,最简单的方法是使用在线可视化工具。为了简单起见,我将使用Chrome开发者工具。
外部函数返回的内部函数
内部函数不一定必须返回才能形成闭包。
Chrome 浏览器不显示本地作用域内的内部函数?
结论
好了,现在你知道闭包是如何形成的了。最后一个例子可能有点让人困惑。所以为了澄清一下,Chrome 没有在外部函数的局部作用域中显示内部函数,并不意味着它不存在(你可以在 Firebox 上看到)。Chrome 可能只在函数被调用、存储或返回时才会列出它。类似地,每个函数在创建时都会与其周围的作用域形成一个闭包,但我们只关心当函数在其周围的函数失效后,跳出词法作用域并在稍后执行时才关注闭包。最终,说所有 JavaScript 函数都是闭包还是所有 JavaScript 函数都有闭包其实并不重要。你应该明白我的意思了。这里有一个有趣的 CodePen 示例,演示了函数柯里化,这是闭包一个很好的实际应用案例。我还建议你阅读MDN 上关于性能方面的注意事项。
原文发布于此处 -
https://mayankav.webflow.io/blog/all-functions-are-closures
文章来源:https://dev.to/mayankav/are-all-javascript-functions-closures-27kp
