JavaScript---循环与闭包
循环与闭包
先看一个demo
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <script> for(var i = 0; i <= 5; i++){ setTimeout(function timer(){ console.log(i); }, i*1000); } </script> </body> </html>
结果会打印出什么? 相信大家都知道 答案是 6个6; 我们本意是打印出:0,1,2,3,4,5 但结果却是6个6, 为什么?首先解释一下为什么打印出6:这里涉及到延时函数的执行机制,虽然延时函数表明是i秒后执行,而i秒是相对与所有可执行代码执行完那一刻开始计时的,也就是等for循环结束之后,延时函数才开始执行。但此时 i= 6;所以会打印出6. 现在在解释一下为什么是6个6:很简单,for循环每执行一次就创建出一个延时函数,执行了6次,就会有6个延时函数。这6个延时函数共享一个作用域(本例中全局作用域) i又累加到6,所以会打印出6个6!!
分析完整个过程,你知道问题出现在哪里吗? 有2个问题 1:每执行一次for循环,应该执行一次延时函数,但并没有。2:所有的延时函数共享一个作用域。
知道问题的地方,那就很好解决了:每循环一次,创建一个作用域 。 关键是如何实现“每循环一次,创建一个作用域”, 其实很简单,每循环一次,就立即执行一次延时函数,立即执行函数(IIFE)就派上用场了 它的原理很“粗暴”:每个延时函数都会将IIFE在每次迭代中创建的作用域 封闭起来。ok,修改一下我们的代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <script> for(var i = 0; i <= 5; i++){ (function(){ setTimeout(function timer(){ console.log(i); }, i*1000); })(); } </script> </body> </html>
在chrome中显示:
咦??? 怎么还是6个6!!! 问题出在哪里? 其实很简单,仔细看一下,我们的IIFE只是一个什么都没有的空作用域(i是全局变量,处于全局作用域中) 它需要有自己的变量,用来在每个迭代中存储i的值 再来改进一下代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <script> for(var i = 0; i <= 5; i++){ (function(){ var j = i; setTimeout(function timer(){ console.log(j); }, j*1000); })(); } </script> </body> </html>
在chrome中显示:
把i值存进来,就可以实现 我们的预期了。到现在为止,代码已经是没有错误的了。但是,还可以优化:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <script> for(var i = 0; i <= 5; i++){ (function(i){ setTimeout(function timer(){ console.log(i); }, i*1000); })(i) } </script> </body> </html>
把i当作参数传进去,我们的代码就更完美了。
2017-3-23 0:19