目录
- 1,什么是闭包
- 2,创建闭包
- 3,如何销毁闭包
- 2.1,自动创建的闭包
- 2.2,手动创建的闭包
- 4,闭包的特点和使用场景
- 3.1,特点
- 3.2,使用场景
- 避免全局变量污染
- 函数柯里化
- 5,闭包经典问题
闭包的定义有许多的说法,下面来介绍下我理解的。
1,什么是闭包
首先,在 js 中闭包是通过作用域链来实现的。
闭包可以看做是一个封闭的空间,用来存储当前作用域的变量,来在其他地方引用。
2,创建闭包
执行函数时,只要在函数中使用了外部数据,就会创建闭包。
验证一下:
function fun() {const i = 1console.log(i) // 断点
}
fun()
const i = 1
function fun() {console.log(i)
}
fun()
而变量是否放入到闭包中,要看其他地方有没有对这个变量的引用。
举例:
const a = 1function fun() {const b = 2const c = 3function fun1() {const d = 4const e = 5function 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() // 局部 name1const 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)
}
注意是块级作用域的原因,此时并没有产生闭包。
以上。