JavaScript闭包
一些常见的闭包题目:
1.经典题目
//1秒后同时输出5个5,每次允许后i++,最后一次i=5;
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000); //若为1000*i,开始输出一个 5,然后每隔一秒再输出一个5,一共5个5
}
2.解决办法
//要想1秒后,同时输出0到4,解决办法一般有两个:
//1,setTimeout函数外面加一层闭包,闭包后变量立即执行。
(function(i) {
setTimeout跑这里
})(i);
//2,把var改为let
2.1. let 不能写在外面
let i; // 如果 let 放在外面, 与 var 无异
for (i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i); // 同时输出 5 个 5
}, 0);
}
3. 立即执行函数要传参,不然无效
(function() { // 不传参 i,无效
想要运行的函数
})(i);
4.使用return的情况, 相当于把立即执行函数写进里面
//1秒后同时输出5个5:
for (var i = 0; i < 5; i++) {
setTimeout(function() { // 同上,这里传进参数i,就可以使1秒后,同时输出0到4
return function() {
console.log(i);
}
}(i), 1000); // 若为1000*i,则开始时输出一个5,然后每隔一秒再输出一个,一共5个5
}
5.console.log后面绑定i参数,会报错,因为setTimeout的第一个参数需要是函数,而立即执行函数,返回的是undefined
//马上输出0到4,只要console.log后面绑定i参数,永远是立刻输出0到4
for (var i = 0; i < 5; i++) {
setTimeout(function(i) {//有没有参数i都没影响
console.log(i);
}(i),1000); //无论这个是1000*i还是多少,都无效
}
6.promise的执行顺序
//立刻输出2,3,5,4,1
setTimeout(function() {
console.log(1)
}, 0);
new Promise(function executor(resolve) {
console.log(2);
for( var i=0 ; i<10000 ; i++ ) {
i == 9999 && resolve();
}
console.log(3);
})
.then(function() {
console.log(4);
});
console.log(5);
7.深入地理解闭包
function f1(){
var n=999;
function f2(){
alert(n);
}
return f2;
}
var result=f1(); //如果直接运行f1()是无效的,必须把return值保存到变量中。
result(); // 999
//f2可以读取f1中的局部变量,那么只要把f2作为返回值,就可以在f1外部读取它的内部变量了
闭包就是能够读取其他函数内部变量的函数。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中:
function f1(){
var n=999;
nAdd=function(){ //全局变量,匿名函数
n+=1;
}
function f2(){
console.log(n);
}
return f2;
}
var result=f1();
result(); // 999
nAdd();
result(); // 1000
//这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。
//f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制回
8.闭包的弊端:
1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
function assignHandler(){
var element=document.getElementById('someElementId');
element.onclick=function(){ //闭包
alert(element.id); //调用了element,它会一直存在于内存中,不会被垃圾回收机制回收掉,所以导致了内存泄漏
}
}
解决办法:尽量引用包含函数的值类型变量,不引用包含函数的引用类型变量,且必须在包含函数的最后将引用变量的值设置为null,断开变量名与对象之间的连接,这样对象便可正常回收。
function assignHandler(){
var element=document.getElementById('someElementId');
var id=element.id;
element.onclick=function(){
alert(id);
}
element=null;
}
9.几个例子理解:
function Foo() {
var i = 0;
return function() {
console.log(i++);
}
}
var f1 = Foo(),
f2 = Foo();
f1();//0
f1();//1
f2();//0 与f1是不同的
var val1=0;
var val2=0;
var val3=0;
for(var i1=1;i1<=3;i1++){ //i1最终为4
console.log('i1='+i1);
var i2=i1; //i2最终为3
console.log('外面i2='+i2);
(function(){
console.log('里面的i2='+i2);
var i3=i2; //i3只存在闭包里,不被销毁
console.log('i3='+i3); //执行到这里后回到上面的i1,循环,直到i1条件不满足
setTimeout(function(){ //1秒后,连续执行3次里面的函数
console.log(i1);//4,4,4
console.log(i2);//3,3,3
console.log(i3);//1,2,3
val1+=i1;
val2+=i2;
val3+=i3;
},1);
})();
}
setTimeout(function(){
console.log(val1); //12
console.log(val2); //9
console.log(val3); //6
},100)