JS - 闭包(Closure)
闭包的定义有许多的说法,下面来介绍下我理解的。
1,什么是闭包
首先,在 js 中闭包是通过作用域链来实现的。
闭包可以看做是一个封闭的空间,用来存储当前作用域的变量,来在其他地方引用。
2,创建闭包
执行函数时,只要在函数中使用了外部数据,就会创建闭包。
验证一下:
function fun() {
const i = 1
console.log(i) // 断点
}
fun()
const i = 1
function fun() {
console.log(i)
}
fun()
而变量是否放入到闭包中,要看其他地方有没有对这个变量的引用。
举例:
const a = 1
function fun() {
const b = 2
const c = 3
function fun1() {
const d = 4
const e = 5
function fun2() {
console.log(a, b, c, d)
}
fun2()
}
fun1()
}
fun()
可以看到创建了3个闭包,分别存储对应作用域的变量。
- 全局
fun
fun1
而变量 e
并没有被放到闭包中,因为没有被引用。
3,如何销毁闭包
闭包的产生会占用空间。那如何销毁闭包,释放空间?
创建闭包分为2种情况,销毁也有所区别。
- 自动创建的闭包
- 手动创建的闭包
2.1,自动创建的闭包
自动创建的闭包,在函数调用完会直接销毁掉。
在上面的例子中,fun()
执行完成后,变量 a
在全局环境中(没有在全局闭包中)正常输出,变量b
会报错。
fun()
console.log(a) // 1
console.log(b) // Error
可以看到已经没有任何闭包存在了,垃圾回收器会自动回收没有引用的变量 b,c,d,e
,不会有内存被占用的情况。
2.2,手动创建的闭包
手动创建的闭包,可以设置在函数调用完依然保留。
先看一个例子:
function getUser() {
const name = '下雪天的夏风'
console.log(name);
}
getUser()
console.log(name); // Error
很明显,局部变量 name
会随着 getUser()
的执行上下文创建而创建,销毁而销毁。所以getUser()
执行完后,name
也就不存在了,打印报错。
修改代码如下:
function getUser() {
const name = '下雪天的夏风'
return function () {
console.log(name)
}
}
const user = getUser()
user() // 下雪天的夏风
调用 user()
为什么能访问到 name
,因为垃圾回收器只会回收没有被引用的变量。原本getUser()
调用完,name
就会被销毁掉,但此时向外部返回了一个匿名函数,该函数引用了 name
,所以name
不会被垃圾回收器回收。
4,闭包的特点和使用场景
3.1,特点
- 通过闭包可以让外部环境访问到函数内部的局部变量。
- 通过闭包可以暂存局部变量,不随着它的上下文环境一起销毁。
因为这2个特点,也就产生了下面的使用场景:
3.2,使用场景
避免全局变量污染
在 js 还无法模块化的时期,多人协作有可能会导致定义的全局变量产生命名冲突。使用闭包可以将变量和对应的功能放到一个独立的空间中,来在一定程度上解决全局变量污染问题。
const name = '全局' // 全局变量
const init = (function () {
const name = '局部name1'
function callName() {
console.log(name)
}
return function () {
callName()
}
})()
init() // 局部 name1
const initSuper = (function () {
const name = '局部name2'
function callName() {
console.log(name)
}
return function () {
callName()
}
})()
initSuper() // 局部 name2
函数柯里化
参考 这篇文章
5,闭包经典问题
for (var i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i)
}, 1000)
}
上面的代码中,我们预期 1s 后输出 0,1,2,但实际会输出3个3。
这个问题就是因为闭包导致的。setTimeout
的回调函数访问了外部变量 i
,形成闭包。而变量 i
只有1个,循环结束后,访问的变量 i
也是同一个。
解决:
方式1:利用立即执行函数,实现 setTimeout
的回调函数不再访问外部变量。
for (var i = 0; i < 3; i++) {
;(function (index) {
setTimeout(function () {
console.log(index)
}, 1000)
})(i)
}
方式2:利用 setTimeout
的第3个参数,实现 setTimeout
的回调函数不再访问外部变量。
for (var i = 0; i < 3; i++) {
setTimeout(
function (index) {
console.log(index)
},
1000,
i
)
}
// 或
for (var i = 0; i < 3; i++) {
setTimeout(console.log, 1000, i)
}
方式3:利用 let
关键字产生的块级作用域,让每次循环都是新的变量i
。
for (let i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i)
}, 1000)
}
注意是块级作用域的原因,此时并没有产生闭包。
以上。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!