看一段代码:
for(var i=1;i<=5;i++){
(function(){
var j=i;
setTimeout(function timer(){
console.log(j);
},j*1000);
})();
}
for(var i=1;i<=5;i++){
(function(j){
setTimeout(function timer(){
console.log(j);
},j*1000);
})(i);
}
for(var i=1;i<=5;i++){
let j=i;
setTimeout(function timer(){
console.log(j);
},j*1000);
}
for(let i=1;i<=5;i++){
setTimeout(function timer(){
console.log(i);
},i*1000);
}
这段代码执行后的结果是什么?
要弄清楚这个问题,先要明白js的事件循环机制。首先参考HTML规范中的一段话:
“为了协调事件
(event),用户交互
(user interaction),脚本
(script),渲染
(rendering),网络
(networking)等,用户代理(user agent)必须使用事件循环
(event loops)。有两类事件循环:一种针对浏览上下文
(browsing context),还有一种针对worker
(web worker)”
事件循环的描述如下:
“一个事件循环有一个或者多个任务队列
(task queues)。任务队列是task的有序列表,这些task是以下工作的对应算法:Events,Parsing,Callbacks,Using a resource(使用资源),Reacting to DOM manipulation(响应DOM)。
每一个任务都来自一个特定的任务源
(task source)。所有来自一个特定任务源并且属于特定事件循环的任务,通常必须被加入到同一个任务队列中,但是来自不同任务源的任务可能会放在不同的任务队列中。
举个例子,用户代理有一个处理鼠标和键盘事件的任务队列。用户代理可以给这个队列比其他队列多3/4的执行时间,以确保交互的响应而不让其他任务队列饿死(starving),并且不会乱序处理任何一个任务队列的事件。
每个事件循环都有一个进入microtask(微任务)
检查点(performing a microtask checkpoint)的flag标志,这个标志初始为false。它被用来组织反复调用‘进入microtask检查点’的算法。”
我们可以看出,任务队列在事件循环中的作用非常重要,那么,我们以将任务队列的执行机制稍作介绍:
待任务可以分为同步任务和异步任务:
同步任务:即立即执行的任务,同步任务一般会直接进入到主线程中执行;
异步任务,即异步执行的任务,例如setTimeout 定时函数,网络请求等都属于异步任务,异步任务会通过任务队列进行管理。
同步和异步任务分别进入不同的执行环境,同步的进入主线程,即主执行栈,异步的进入 任务队列 。主线程内的任务执行完毕为空,会去 Event Queue 读取对应的任务,推入主线程执行。 上述过程的不断重复就是我们说的 Event Loop (事件循环)。
基于以上的机制,我们来分析上面的代码:
代码中是五个异步任务,没有同步任务,因此都进入异步队列,再以先进入先出的顺序执行,我猜测运行结果是顺序输出4个“1 2 3 4 5”串:即
1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5
但是,结果不是这样!而是:
1 1 1 1 2 2 2 2 3 3 3 3 4 4 4 4 5 5 5 5
即先输出4个1,再输出4个2,以此类推;
Why?!!
关于这个问题,我暂时没有想清楚,先记录下来。
引用文章:https://segmentfault.com/a/1190000010622146;https://www.cnblogs.com/yugege/p/9598265.html