# 一、对闭包的理解
闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,创建的函数可以访问到当前函数的局部变量。
闭包就是每次调用外层函数时,临时创建的函数作用域对象。 因为内层函数作用域链中包含外层函数的作用域对象,且内层函数被引用,导致内层函数不会被释放,同时它又保持着对父级作用域的引用,这个时候就形成了闭包。
function add() {
var a = 1;
function fn() {
return a++;
}
return fn;
}
// console.log(add()()); //1
// console.log(add()()); //1
// console.log(add()()); //1
// console.log(add()()); //1
var c = add(); // 变量c是全局作用域 函数执行完后不会被释放
console.log(c()); // 1
console.log(c()); // 2
console.log(c()); // 3
console.log(c()); // 4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 二、使用场景
任何闭包的使用场景都离不开这两点:
- 创建私有变量
- 延长变量的生命周期
一般函数的词法环境在函数返回后就被销毁,但是闭包会保存对创建时所在词法环境的引用,即便创建时所在的执行上下文被销毁,但创建时所在词法环境依然存在,以达到延长变量的生命周期的目的
经典面试题:循环中使用闭包解决 var 定义函数的问题
for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i)
}, 1000)
}
1
2
3
4
5
2
3
4
5
首先因为 setTimeout
是个异步函数,所以会先把循环全部执行完毕,这时候 i
就是 6 了,所以会输出一堆 6。解决办法有三种:
- 第一种是使用闭包的方式
for (var i = 1; i <= 5; i++) {
(function (j) {
setTimeout(()=> {console.log(j);}, 1000);
})(i);
}
1
2
3
4
5
2
3
4
5
在上述代码中,首先使用了立即执行函数将 i
传入函数内部,这个时候值就被固定在了参数 j
上面不会改变,当下次执行 timer
这个闭包的时候,就可以使用外部函数的变量 j
,从而达到目的。
- 第二种就是使用
setTimeout
的第三个参数,这个参数会被当成timer
函数的参数传入。
for (var i = 1; i <= 5; i++) {
setTimeout(
(j) => {console.log(j);},
1000,
i
);
}
1
2
3
4
5
6
7
2
3
4
5
6
7
- 第三种就是使用
let
定义i
了来解决问题了,这个也是最为推荐的方式
for (let i = 1; i <= 5; i++) {
setTimeout(() =>{console.log(i)}, 1000)
}
1
2
3
2
3
使用let
关键字声明变量i
:使用let
(和const
)关键字声明的变量是具有块作用域的(块是{}之间的任何东西)。
在每次迭代期间,i
将被创建为一个新值,并且每个值都会存在于循环内的块级作用域。
# 三、注意事项
如果不是某些特定任务需要使用闭包,在其它函数中创建函数是不明智的,因为闭包在处理速度和内存消耗方面对脚本性能具有负面影响。
闭包的作用:用来保护变量不被污染。
闭包的缺点:性能问题,容易造成内存泄漏(内存满了就会泄露)。