那些for循环里面的闭包

作者 likaiqiang 日期 2017-03-15
那些for循环里面的闭包

说好的春天来了,花儿开了,草儿绿了,到处都是草长莺飞的景象。我为啥会在这时候感冒了呢,好吧,跑题了。

开场白,一个简单的for循环

for(var i = 0;i<5;i++){
console.log(i)
}

输出 0 1 2 3 4

再来一个

for(var i = 0;i<5;i++){
setTimeout(function(){
console.log(i)
},1000)
}

输出 5 5 5 5 5

第三个

for(var i = 0;i<5;i++){
(function(i){
setTimeout(function(){
console.log(i);
},1000)
})(i)
}

输出 0 1 2 3 4

第四个

for(var i = 0;i<5;i++){
setTimeout(function(i){
console.log(i);
},1000)
}

输出 undefined undefined undefined undefined undefined

第五个

for(var i=0;i<5;i++){
setTimeout((function(i){
console.log(i)
})(i),1000)
}

输出 0 1 2 3 4

迷糊没,没迷糊,没迷糊咋晃悠呢!!!

第一个例子就不说了

从第二个开始,js中的闭包就上场了。

什么是闭包,闭包是一个函数在创建时允许该自身函数访问并操作该自身函数之外的对象时所创建的作用域。换句话说,闭包可以让函数访问所有的变量和函数,只要这些变量和函数存在与该函数声明时的作用域内就行。

引用javascript忍者秘技上面的一个例子:

html

<body>
<ul id='list'></ul>
</body>

javascript

function assert(bool,text){
var list = document.getElementById('list');
var li = document.createElement('li');
li.textContent = text;
list.appendChild(li);
bool ? li.style.color = 'green' : li.style.color = 'red';
}
var outerValue = 'ninja';
var later;
function outerFunction(){
var innerValue = 'samural';
function innerFunction(){
assert(outerValue,'Inner can see the ninja');
assert(innerValue,'inner can see the samural');
}
later = innerFunction;
};
outerFunction();
later();

assert函数在那本书里被称作‘断言’,如果第一个参数为true,第二个参数为绿色,反之,第二个参数为红色。

浏览器输出结果:

有没有疑惑,执行later时,实际上相当于 window.innerFunction() ,按常规思路,outerFunction里面的innerValue理应访问不到。但是,innerFunction在创建时同时也创建了一个闭包,innerFunction的作用域为outerFunction内部,这个闭包使得innerFunction里面的变量无论在什么时候都可以访问处于innerFunction函数外面,并且处于outerFunction函数里面的变量。

用张图来表示

外围圆是outerFunction,也就是innerFunction在创建时的作用域,外围圆实际相当于innerFunction在创建时形成的那个闭包。

无论在什么时候,哪怕是outerFunction已经不存在了,innerFunction里面仍旧可以完成对outerFunction(闭包)内变量的读写。

有了这个概念,再来看那几个for循环。

第二个:

for(var i = 0;i<5;i++){
setTimeout(function(){
console.log(i)
},1000)
}

总共有5个setTimeout,形成了5个闭包,看图:

外围矩形为5个setTimeout的回调函数形成的闭包(winsow),等到5个回调执行时,for循环早就完了,所以会输出 5 5 5 5 5;

第三个:

同样的setTimeout的回调函数形成了5个闭包,但是由于立即执行函数的影响,闭包的作用域从window变成了五个小圆,所以最后输出 0 1 2 3 4

这样写,是不是好理解一点

for(let i = 0;i<5;i++){
setTimeout(function(){
console.log(i);
},1000)
}

其实这个和第三个道理是一样的,我们知道let和var的一个区别是,let有块级作用域,这就和上面的立即执行函数作用一样了,当然输出也一样了。

跳过第四个,先看第五个

for(var i=0;i<5;i++){
setTimeout((function(i){
console.log(i)
})(i),1000)
}

把这个立即执行函数写开

for(var i=0;i<5;i++){
var a = function(i){
console.log(i);
}

setTimeout(a(i),1000)
}

有函数就有闭包,循环了5次,创建了5个闭包

同样这张图,意思是一样的,所以输出 0 1 2 3 4

最后再说第四个,这哥们是来添乱的,setTimeout的回调函数是不可以传参的,没有实参的形参当然是undefined,这压根和闭包没关系!!!