引子
有一个函数,里面使用var在一个for循环10次,循环里面有一个时间为0的定时器,定时器中输出i。如下两段代码:
1:代码示例1
问题:为什么输出的是10个10?
解题思路:js的代码虽然是由上至下,但是遵循自己的运行机制。****在for循环中,由于定时器是延时任务,会等到主线程的for循环执行完成后执行,而for循环执行完是i为10 的时候,所以这时候打印出来的是10 个 10。
for(var i = 0; i<10; i++){
setTimeout(() => {
console.log(i);
}, 0);
}
// 输出10个10
2:代码示例2
问题:为什么输出的是10个10?
解题思路:直接看test的函数,函数中将循环的一个输出i的匿名函数赋值给数组arr对应i的下标,理论上应该是在j的循环中输出对应的0-9
,但是实际上输出的依旧为10个10,这是由于匿名函数虽然是立即执行的,但这里是arr[i]被赋值为一个函数,此时这个赋值语句是声明和循环语句,里面的函数体内容是不执行的,只有在调用这个函数时,这个函数才执行。
function test(){
var arr = [];
for(var i = 0; i < 10; i++){
arr[i] = function(){
console.log(i);
}
}
return arr;
}
var myArr = test();
for(var j = 0;j<10;j++){
myArr[j]();
}
一、闭包是什么?
根据上面的例子我们得出下面结论:
1:在一个作用域中获取其他作用域的变量或者方法的一种形式就叫闭包。
(定义)
2:当内部函数被保存在外部时,将会生成闭包。
(由来)
3:闭包会导致原有作用域链不释放,造成内存泄漏。
(缺点)
4:从外部读取函数内部的变量、将创建的变量的值始终保持在内存中、封装对象的私有属性和私有方法。
(作用)
二、使用
在知晓的闭包的基本定义和作用后,我们需要去使用它来解决我们遇到的问题。如开头的引子中的代码
1.代码示例1解决
方式二解决思路:使用匿名函数立即执行的原理,通过闭包的方式将i传给定时器的匿名函数,打印的就是遍历传递时候的数据,也就是0-9。
// 三种方式输出的都是0-9
// 方式一 (把var改成let即可)
for(let i = 0; i<10; i++){
setTimeout(() => {
console.log(i);
}, 0);
}
// 方式二
for(var i = 0; i<10; i++){
(function (j){
setTimeout(() => {
console.log(j);
}, 0);
})(i)
}
// 方式三 (就是把匿名函数包裹的括号变成了~,和方式二是一样的)
for(var i = 0; i<10; i++){
~function(j){
setTimeout(() => {
console.log(j);
}, 0);
}(i)
}
2.代码示例2解决
解决思路:同上(就是把你要执行的包裹上匿名函数,函数执行的时候把遍历的值传递过去即可)
function test(){
var arr = [];
for(var i = 0; i < 10; i++){
(function(j){
arr[j] = function(){
console.log(j);
}
})(i)
}
return arr;
}
var myArr = test();
for(var j = 0;j<10;j++){
myArr[j]();
}
三:总结
1:闭包的优点
(1)能够访问其他函数内部变量。
(2)变量长期驻扎在内存中,不会被内存回收机制回收,即延长变量的生命周期;
(3)可以避免定义全局变量所造成的变量污染
2:闭包的缺点(优点对应的缺点)
(1)使用闭包,有内存泄露的风险。
(2)大量使用闭包,由于不会被内存回收机制回收,会占有大量内存空间。
内存泄漏(Memory leak):内存泄漏是一种比喻的说法,它并非指内存在物理上的数据泄漏,而是应用程序分配某段内存后,由于疏忽或错误造成程序未能释放且不再使用的内存,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。以上这种情况称之为内存泄漏。
如何避免闭包引起的内存泄漏
给之后不使用的匿名函数赋值变量,随后将不使用的局部变量赋值为null,如示例1中的匿名函数定义为a,随后将a置为null
for(var i = 0; i<10; i++){
let a = (function (j){
setTimeout(() => {
console.log(j);
}, 0);
})(i)
a = null; // 防止内存泄漏
}