一、闭包是什么
闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,创建的函数可以访问到当前函数的局部变量。
二、闭包的特点
-
(1)让外部访问函数内部变量变成可能
-
(2)变量会常驻在内存中
-
(3)可以避免使用全局变量,防止全局变量污染;
三、闭包的好处和坏处
-
好处:可以读取其他函数内部的变量,并将其一直保存在内存中。
-
坏处:可能会造成内存泄漏或溢出。
四、闭包有两个常用的用途
-
(1)闭包的第一个用途是使我们在函数外部能够访问到函数内部的变量。通过使用闭包,可以通过在外部调用闭包函数,从而在外部访问到函数内部的变量,可以使用这种方法来创建私有变量。
-
(2)闭包的另一个用途是使已经运行结束的函数上下文中的变量对象继续留在内存中,因为闭包函数保留了这个变量对象的引用,所以这个变量对象不会被回收。
比如,函数 AFunc
内部有一个函数 BFunc
,函数 BFunc
可以访问到函数 AFunc
中的变量,那么函数 BFunc
就是闭包。
function AFunc() {
let aaa = 'aaa';
return function BFunc () {
console.log('闭包内console:', aaa);
}
}
AFunc()(); // '闭包内console:aaa'
在 JS 中,闭包存在的意义就是让我们可以间接访问函数内部的变量。经典面试题:循环中使用闭包解决es5
的 var
定义函数的问题
for (var i = 1; i <= 10; i++) {
setTimeout(function loop() {
console.log(i)
}, i * 500); // 每500ms执行一次
}
首先因为 setTimeout
是个异步函数,所以会先把循环全部执行完毕,这时候 i
就是 11 了,所以会输出一堆 11。解决办法有三种:
第一种是使用闭包的方式
for (var i = 1; i <= 10; i++) {
;
(function(j) {
setTimeout(function loop() {
console.log(j)
}, j * 500); // 每500ms执行一次
})(i)
}
上面的写法等同于
function func(i) {
// 此时的Loop形成了闭包
setTimeout(function loop() {
console.log(i)
}, i * 500); // 每500ms执行一次
}
for (var i = 1; i <= 10; i++) {
;
func(i); // 此时的i传入func作为func函数的参数,等同于他内部的变量
}
在上述代码中,首先使用了立即执行函数将 i 传入函数内部,这个时候值就被固定在了参数 j 上面不会改变,当下次执行 loop
这个闭包的时候,就可以使用外部函数的变量 j,从而达到目的。
第二种就是使用 setTimeout
的第三个参数,这个参数会被当成 loop
函数的参数传入。
for (var i = 1; i <= 10; i++) {
setTimeout(
function loop(j) {
console.log(j)
},
i * 500,
i // 作为loop函数的参数传入,等同于内部自定义了变量,因此可以正确记录数字i的自增
)
}
第三种就是使用 let
定义 i 来解决问题了,这个也是最为推荐的方式
for (let i = 1; i <= 10; i++) {
setTimeout(function loop() {
console.log(i)
}, i * 500)
}
PS:let
和 var
有何不同
也可以参考:https://blog.csdn.net/qq_43145310/article/details/125737211
let与var最大的区别是,let声明的范围是块作用域,而var声明的范围是函数作用域。let不允许重复声明。函数作用域 > 块作用域。
var和let都用于声明变量,不同的是:
-
(1)
let
声明的变量不能重复声明,而var
可以 -
(2)
let
声明的变量不能变量提升,而var
可以 -
- 当使用
var
声明一个var变量
时,该变量会被提升到作用域的顶端,但是赋值的部分不会提升
。
- 当使用
console.log(a);
var a = 'aaa';
-
- 在声明a的语句之前就可以输出a变量,值为
undefined
,这就是变量提升,而使用let
声明变量时不能变量提升。
- 在声明a的语句之前就可以输出a变量,值为
-
(3)
var
是函数作用域,let
是块状作用域 -
- 在函数里使用
var
声明了一个变量,那么这个变量在整个函数内都是有效的,例如在for循环里用var
声明一个变量,在for循环外也是可以使用的。但是let
作用域是块状作用域,只在作用域里有效,例如在for循环里用let
声明一个变量,在for循环外面是不能被访问的。
- 在函数里使用
-
(4)在全局环境下使用
let
声明的变量不属于顶层对象(未能验证成功,待确认) -
- 顶层对象,在
浏览器
中指的是window
,在node环境
中指的是global
对象。
- 顶层对象,在
-
var
声明的变量属于顶层对象window
,因此可以通过 window. 变量名 来访问这些变量,而let
和const
声明的变量不能这样访问。
var name = 'Jerry';
console.log(window.name); // 'Jerry'
let age = 35;
console.log(window.age); // undefined
五、使用闭包的注意点
-
(1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,
在退出函数之前,将不使用的局部变量全部删除
。 -
(2)
闭包会在父函数外部,改变父函数内部变量的值
。所以,如果你把父函数当作对象(object)
使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。