首先看一段代码
for(var i = 0; i < 10; i++) { console.log(i); }
这段代码输出0, 1, 2, 3, 4, 5, 6, 7, 8, 9
接着
for(var i = 0; i < 10; i++) { setTimeout(function() { console.log(i); }, 1000); }
这段代码输出的全是10
这里引用原文的话:详情https://www.douban.com/note/293295975/
因为setTimeout是异步的!
你可以想象由于setTimeout是异步的, 因此我们将这个for循环拆成2个部分
第一个部分专门处理 i 值的变化, 第二个部分专门来做setTimeout
因此我们可以得到如下代码
// 第一个部分
i++;
...
i++; // 总共做10次
// 第二个部分
setTimeout(function() {
console.log(i);
}, 1000);
...
setTimeout(function() {
console.log(i);
}, 1000); // 总共做10次
这样一拆后, 我相信你肯定知道之前那个for循环的运行结果了.
由于循环中的变量 i 一直在变, 最终会变成10, 而循环每每执行setTimeout时, 其中的方法还没有真正运行, 等真正到时间执行时, i 的值已经变成 10 了!
i 变化的整个过程是瞬间完成的, 总之比你异步要快, 就算你setTimout是0毫秒也一样, 会先于你执行完成.
接下来上自己的例子
var nodes = document.getElementsByTagName("li"); for(i = 0;i<nodes.length;i+= 1){ nodes[i].onclick = function(){ console.log(i+1);//不用闭包的话,值每次都是4 }; }
这段代码无论点击哪个 li 都只输出4;
这里我个人的理解,根据浏览器JS解析引擎;
JS解析引擎一开始并不是直接从上到下开始执行代码
而是搜集代码中的全局变量和函数块存起来。
接着再从上到下执行代码。
第一行代码
nodes = document.getElementsByTagName("li");
给变量nodes赋值,接着for循环
for(i = 0;i<nodes.length;i+= 1){ nodes[i].onclick = function(){ console.log(i+1);}; }
nodes[i].onclick为点击事件添加处理函数(注意!function(){ console.log(i+1);}并没有执行,而是函数的引用赋值给nodes[i].onclick而已)
所以当点击事件触发,浏览器执行 function(){ console.log(i+1);}时查找 i 的值就是for循环执行后的值。
也可以理解成这样
for(i = 0;i<nodes.length;i+= 1){
nodes[i].onclick = a;
}
这就是说明 console.log(i+1) 只输出循环的之后的值得原因在于:(!function(){ console.log(i+1);}并没立即执行)
所以为了解决该问题
i 作为参数传入来固定这个变量的值, 让其保留下来
需要作以下修改
function a (i) { console.log(i); } for(var i = 0; i < 10; i++) { setTimeout(a(i), 1000); }
这样已经可以输出0, 1, 2, 3, 4, 5, 6, 7, 8, 9了。
再接着修改
for(var i = 0; i < 10; i++) { setTimeout((function a (i) {console.log(i);})(i), 1000); }
var nodes = document.getElementsByTagName("li"); for(i = 0;i<nodes.length;i+= 1){ nodes[i].onclick = (function(){ console.log(i+1);//不用闭包的话,值每次都是4 };)(i) }
其实上诉解决办法就是闭包啦。
不过这样也会带来副作用,例如setTimeout并没有延时就直接输出了,点击事件并没有点击也触发了。
闭包的作用
先上代码
function say667() { // Local variable that ends up within closure var num = 666; var sayAlert = function() { alert(num); } num++; return sayAlert; } var sayAlert = say667(); sayAlert()//执行结果应该弹出的667
执行say667()后,say667()闭包内部变量会存在,而闭包内部函数的内部变量不会存在
使得Javascript的垃圾回收机制GC不会收回say667()所占用的资源
因为say667()的内部函数的执行需要依赖say667()中的变量
这是对闭包作用的非常直白的描述