JS 函数的执行时机之setTimeout
setTimeout()
的意思是设置一个定时器,该定时器在定时器到期后执行一个函数或指定的一段代码。
例:
如下代码会打出6个6。
let i = 0 for(i = 0; i<6; i++){ setTimeout(()=>{ console.log(i) },0) }
//打印结果6,6,6,6,6,6
其原因是因为:
setTimeout
函数会优先执行之前的事件,最后再执行后续的事件。而之前的事件是i
循环,显然得等i
循环执行完毕再执行打印出i
的这个函数,当i
等于5
时,因为小于6
会再执行一次,直到i
等于6,一共执行了6次,根据前面所诉会打出6个6
,而非6个5
。
不过,为了让上面的结果变成 0
、1
、2
、3
、4
、5
,可以使用如下的代码。
for(let i = 0; i<6; i++){ setTimeout(()=>{ console.log(i) },0) }
//打印结果0,1,2,3,4,5
在这个情况下,let
关键字劫持了for
循环的块作用域。通俗解释为JS
在for
和let
一起使用的时候会添加东西,每次进入循环的时候会多创建一个i
,把i
复制一份留在这个空间用于打印,这个所谓的i
不是会变的i
,是变化之前的拷贝,不跟随变化的i
变化,所以一共变化了5次,不会到6
,打印出0
、1
、2
、3
、4
、5
。
这使得setTimeout
毫无意义。
我还提供了2种方法可以打印出0
、1
、2
、3
、4
、5
的方法供大家参考。
1. 使用立即执行函数
for(var i = 0; i < 5; i++) { (function(i) { setTimeout(function () { console.log(i); }); })(i) } console.log('a');
//打印结果0,1,2,3,4,5
和上面有点类似,这个是利用闭包的原理,闭包使一个函数可以继续访问它定义时的作用域。而这个新生成的作用域将每一次循环的当前i
值单独保存了下来。
2. 使用try...catch语句
for(var i = 0; i < 5; i++) { try { throw(i) } catch(j) { setTimeout(function () { console.log(j); }); } }
//打印结果0,1,2,3,4,5
这是因为try...catch
语句的catch
后面的花括号是一个块作用域,和let
的效果一样。所以在try
语句块里抛出循环变量i
,然后在catch
的块作用域里接收到传过来的i
,就可以将循环变量保存下来,实现类似闭包和let
的效果。