浅谈JS中的闭包
对于 JavaScript 初学者来说,JavaScript 有着这样一个概念十分重要,几乎是每个前端工程师面试时必然会遇到的一个问题,JS 的闭包(closure)。今天我们就来浅谈一下 JS 中的闭包究竟是怎样的东西。
闭包的定义
要弄懂什么是闭包,我们需要先给闭包下一个定义。
根据 MDN 文档上的定义:
一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。
可以看到,在这个定义中,我们引入了一些特殊的概念——作用域。已经有过编程基础的同学应该不会对这个词感到陌生,在其他的语言中也同样存在作用域的概念,那么接下来,我们将先介绍什么是 JavaScript 中的词法作用域。
词法作用域
首先,我们来考虑这样一个例子:
1 |
|
在这个例子中,我们通过调用 func
函数,创建了一个局部变量 num
和一个叫做 plus
的函数。由于plus
是在func
内部创建的,因此也只能在func
内部被调用。对于plus
函数来说,它没有自己的局部变量,但它能够调用其父函数func
中的局部变量num
。
我们把这段代码执行一下就能发现,在控制台中打印了数字 2 。这个 词法作用域 的例子描述了分析器如何在函数嵌套的情况下解析变量名。词法(lexical)一词指的是,词法作用域根据源代码中声明变量的位置来确定该变量在何处可用。嵌套函数可访问声明于它们外部作用域的变量。
理解闭包
有了上面的例子,我们就能理解闭包究竟是什么样的东西了。
继承我们上面的例子,虽然我们已经能够在外部访问到num
变量,但是每次执行func
函数实际上都创建了新的num
变量,我们想让num
持续地自加,应当如何实现?其实只需要对上方的代码做一点小小的修改即可:
1 |
|
经过修改后的func
函数,直接返回了plus
方法,其后我们可以直接通过调用这个方法来实现num
的持续自加。
看到这里,相信大家其实都很明白了:
所谓闭包,就是通过一个函数包裹局部变量,并返回一个嵌套函数,外部环境通过调用返回的嵌套函数来达到访问并操作函数内部的局部变量。
最后给大家看一个稍微复杂一点的闭包例子:
1 |
|
相信大家也能轻松地理解上述代码的执行过程。
闭包的实际使用
在前端开发中,闭包的使用是非常灵活的,比如说对于防抖(debounce)和节流(throttle)函数的封装:
1 |
|
注意事项
性能考量
如果不是出于某些特殊目的不得不使用闭包,在其他函数中创建新的函数是不推荐的做法。因为闭包在处理速度和内存消耗方面对 JS 的性能具有负面影响。
内存泄漏
如果很不幸,您的代码中必须大量使用闭包,请务必注意是否有内存泄漏的风险。如果可能,请务必在不需要闭包后将闭包中的引用变量置为 null ,以便 JS 能将它们视为不再需要的垃圾回收。