当你能从浪费时间中获得快乐时,就不算浪费时间
Examples
//test1 ==> 0 1 2 3 4 5 6 7 8 9
for(var i = 0 ;i <10;i++){
setTimeout(
console.log(i),
0);
}
//test2 ==> 10个10
for(var i = 0;i<10;i++){
setTimeout(() =>{
console.log(i)
},0)
}
//test3 ==> 10个10
for(var i = 0 ;i <10;i++){
setTimeout(
'console.log(i)',
0);
}
详解
王德发,为什么for循环里一个setTimeout会出现这么多的花样,下面我们好好解释解释~~
在这之前我们还需要好好了解一下js的异步机制以及setTimeout参数的问题~
js的异步机制
地球人都知道js是个单线程环境,顾名思义就是js在同一时间只能干一件事,那么当任务非常多的时候,就需要有序的进行排队,直到上一个任务做完,才能做下一个任务。
当然所有事都得有个轻重缓急,js为了更好的完成任务,便将所有的任务分为了同步任务与异步任务两大类。
同步任务指的是,在主线程上排队的任务,当前一个任务执行完毕便执行后一个任务。异步任务指的是,在任务队列中排队的任务,只有任务队列通知主线程,某一个异步任务可以执行了,该任务才会进入主线程。
在异步任务中,还分为宏任务和微任务,当宏任务和微任务都处于任务队列中时,微任务的优先级大于宏任务,即在同一次事件循环中,先执行微任务再执行宏任务。
一般来说宏任务有setTimeout,setInterval,UI rendering,微任务有promise,requestAnimationFrame。
我们再梳理一下,js的执行机制如下:
- 所有同步任务在主线程上执行,形成一个执行栈
- 在主线程之外,还有一个任务队列,异步任务都放置在其中,等待主线程的调用栈为空,再依次从队列中执行任务
- 检测队列中的微任务队列是否为空,若不为空,则取出一个微任务入栈执行,反之,则取出宏任务执行
- 执行完宏任务队列中的一个任务后,会继续检测微任务队列是否为空,如果有新插入的任务,就继续执行第三步,如果微任务队列为空,则继续执行宏任务的下一个任务,然后再继续循环执行第四步
这整个过程循环往复,所以又被称为Event Loop(事件循环)。
setTimeout的第一个参数问题
一般来说,我们使用setTimeout的时候,第一个参数是个函数,但是对于setTimeout来说,并没有规定第一个参数一定要是函数,它还允许使用字符串作为第一个参数,在js内部中会使用eval函数来动态执行一段字符串脚本~
setTimeout(
'console.log(0)',
0)
==>
setTimeout(
eval('console.log(0)'),
0)
test1解析
由此我们可以看test1的输出结果,for循环中的setTimeout是个异步任务,需要等到主线程的同步任务执行完才能执行,而console.log()立即执行函数是个同步任务,所以会跟着for循环同步执行,所以结果即0,1,2,3,4,5,6,7,8,9。
test2解析
同理,我们看test2的输出结果,当开始执行setTimeout异步任务的时候,for循环已经结束了,并且由于var声明的变量不具有快级作用域的特点,所以当for循环结束的时候,i为10,结果就会输出10个10,如果我们吧var换成let,那么结果就会变成test1的结果。
test3解析
与test1的区别在于,setTimeout第一个参数传递了一个字符串,而不再是一个立即执行函数,所以结果输出10个10。