闭包(closure)在 JavaScript 中可以说是无处不在,但由于我们无法直接观测到它,所以就导致我们经常忽视了它的存在,以致于难以掌握。
事实上,每当我们创建一个函数闭包也会随之产生,它是基于词法作用域书写代码时自然产生的结果。
const a = 1
function foo() {
console.log(a)
}
上面这段代码有闭包吗?如果有,你能说说它具体是什么吗?
接下来,让我们一起来探究一下到底什么是闭包。
基本概念
我们先来看看 MDN 文档是如何描述闭包这个概念的:
MDN 把闭包的基本概念描述得已经非常清楚了,但是个人觉得还缺少一条关键信息,那就是在什么情况下闭包会产生作用。
在《你不知道的 JavaScript(上卷)》一书中是这么定义闭包的:
这句话的后半段非常重要,一来加强说明了闭包一定会在函数创建时产生,再者是指出了闭包在什么情况下发挥作用。
这句话前半段中的词法作用域可以理解为函数所在位置的作用域,但实际上还包含了在该函数中能够通过作用域访问到的所有变量(引用)。
综上所述,我们可以将闭包的基本概念总成为两点:
-
闭包会在函数被创建时,自动根据所在的词法作用域产生。
-
闭包主要是在函数不在所在词法作用域中执行的情况下起作用。
若隐若现的闭包
让我们先来看这段代码:
function foo() {
const a = 1
function bar() {
console.log(a)
}
bar() // 1
}
foo()
调用 foo()
函数时,bar()
函数也被执行,基于词法作用域的查找规则,bar()
函数可以访问外部作用域(foo()
函数的作用域)中的变量 a
。
这段代码中有闭包吗,从上文中闭包的基本概念来看,肯定产生了闭包,只是我们很难找到它们的踪迹。
第一个闭包是 foo()
函数在创建时产生的,它引用了外部全局作用域,第二个闭包是 bar()
函数在创建时产生的,它引用了 foo()
函数的作用域以及外部全局作用域。
再来看这段代码:
function foo() {
const a = 1
function bar() {
console.log(a)
}
return bar
}
const baz = foo()
baz() // 1
与上一段代码不同,在调用 foo()
函数时,我们将内部的 bar()
函数引用作为返回值赋值给了变量 baz
,当我们调用 bar()
函数时,实际上调用的就是 bar()
函数本身。
一般来说,当 foo()
函数执行完时,内部作用域会被销毁,垃圾回收机制会被触发,变量 a
占用的内存会被释放。但是当我们在全局作用域中调用 bar()
函数后,还是打印出了变量 a
的值,这就说明 foo()
函数的内部作用域没有被销毁,变量 a
还在内存中。
这就是闭包的强大之处,bar()
函数产生的闭包阻止了 foo()
函数的内部作用域被销毁。这也佐证了闭包基本概念中的第二点,闭包主要是在函数不在所在词法作用域中执行的情况下起作用。
我们再来看看其他情况:
function sleep(fn, delay, ...data) {
setTimeout(function bar() {
fn(data)
}, delay)
}
sleep((who) => {
console.log( `${who} wake up`)
}, 1000, 'jack')
// 约一秒后
// jack wake up
这段代码中,当 sleep()
函数执行完后,它的作用域也不会立即消失,这是因为 bar()
函数依然保持着有 sleep()
函数作用域的闭包。
1000 毫秒后,bar()
函数顺利地从 sleep()
函数作用域中找到了变量 fn
和变量 data
。
像类似 setTimeout()
这样的异步任务中,我们很容易发现闭包的影子,因为它们都使用了回调函数,该函数中又引用了外层作用域的变量。
const foo = (function (param) {
return function bar () {
console.log(param)
}
})('A')
foo() // A
上面这段代码使用了 IIFE 立即执行函数,当该函数执行完时,它创建的作用域会一直保留,因此 bar()
函数能够访问到变量 param
,当然这也是闭包的作用。
原来如此,IIFE 也是容易发现闭包的地方,闭包真是无处不在。
闭包的副作用?
长久以来,闭包一直被一部分人认为是最容易造成内存泄露的罪魁祸首。
const foo = (function () {
const a = 1
return function () {
console.log(a)
}
})()
这段代码中,只要变量 foo
指向的匿名函数存在,分配给变量 a
的内存就永远不会得到释放。
的确,滥用闭包会有内存泄露的危险,但是更加值得我们关注的是它带来的好处。
总之,我们应该去认识和拥抱它,并利用好这一特性带给我们的魔力,用过的人都说好!
小结
本文主要探究了 JavaScript 中闭包这一晦涩难懂的特性,总结了它的基本概念以及特点。
以上就是本文的全部内容,希望对你有所帮助。
本人水平有限,如有错误或有争议的地方,请及时指出。
最后,感谢阅读,欢迎交流分享!
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!