Event Loop 事件循环机制 详解(一)
单线程意味着,javascript代码在执行的任何时候,都只有一个主线程来处理所有的任务。
而非阻塞则是当代码需要进行一项异步任务(无法立刻返回结果,需要花一定时间才能返回的任务,如I/O事件)的时候,主线程会挂起(pending)这个任务,然后在异步任务返回结果的时候再根据一定规则去执行相应的回调。
单线程是必要的,也是javascript这门语言的基石,原因之一在其最初也是最主要的执行环境——浏览器中,我们需要进行各种各样的dom操作。试想一下 如果javascript是多线程的,那么当两个线程同时对dom进行一项操作,例如一个向其添加事件,而另一个删除了这个dom,此时该如何处理呢?因此,为了保证不会 发生类似于这个例子中的情景,javascript选择只用一个主线程来执行代码,这样就保证了程序执行的一致性。
浏览器环境下,会维护一个任务队列,当异步任务到达的时候加入队列,等待事件循环到合适的时机执行,即事件的循环机制实际上,js 引擎并不只维护一个任务队列,总共有两种任务
Task(macroTask):
microTask:
$(function(){ console.log("5"); var set1 = setTimeout(function () { var set2 = setTimeout(function () { console.log("2"); var set3 = setTimeout(function() { console.log("10"); }, 0) }, 0); var set4 = setTimeout(function () { console.log("3"); }, 0); console.log("1"); }, 0); var op = new Promise(function (resolve) { console.log("8"); resolve(); }).then(function () { console.log("9"); }).then(function () { console.log("12"); }).then(function () { var set5 = setTimeout(function() { console.log("13"); }, 0); }); console.log("6"); var set6 = setTimeout(function () { var set7 = setTimeout(function () { console.log("7"); }, 0) console.log("4"); }, 0) $("body").mousemove(function(){ console.log("11"); }); }) // 5 8 6 9 12 (11) 1 4 13 2 3 7 10
我们来分析一下执行流程吧: 图中Main可以和Mis是合并的
我们再来分析一下有时间的情况:
$(function(){ console.log("5"); var set1 = setTimeout(function () { var set2 = setTimeout(function () { console.log("2"); var set3 = setTimeout(function() { console.log("10"); }, 2) }, 2); var set4 = setTimeout(function () { console.log("3"); }, 1); console.log("1"); }, 3); var op = new Promise(function (resolve) { console.log("8"); resolve(); }).then(function () { console.log("9"); }).then(function () { console.log("12"); }).then(function () { var set5 = setTimeout(function() { console.log("13"); }, 0); }); console.log("6"); var set6 = setTimeout(function () { var set7 = setTimeout(function () { console.log("7"); }, 2) console.log("4"); }, 2) $("body").mousemove(function(){ console.log("11"); }); })
分析:由于宏队列中由于加上了定时时间,因此在本轮次的宏队列中时间排序优先级大于执行顺序优先级。 在例子中有奇特的样例,在执行完S6的时候队列中存在 本轮次: S1(3ms), 下一轮次: S7(2ms),S1先执行于S2,可见当前轮次中的所有任务优先级大于下一轮次的所有任务;本轮次的S7(2ms,rank 10),S2(2ms,ran 11), 由于时间相同,则比较rank(执行顺序),所以很容易的知道S7优先执行于S2。
注意: 由于时间设置的太小,因此可能浏览器的显示可能会出现一些问题,所以在实验的时候 (代码时间 * 100) 效果较好。
总结:
实际上, microTasks 和 Tasks 并不在同一个队列里面,他们的调度机制也不相同。比较具体的是这样:
1. event-loop start
2. microTasks 队列开始执行
3. 检查 Tasks 是否已经执行完成,否则跳到 4,是则跳到 6
4. 从 Tasks 队列抽取一个任务,执行
5. 检查 microTasks 是否还有任务,若有则跳到 2,无则跳到 3
6. 结束 event-loop
也就是说,microTasks 队列在一次事件循环里面不止检查一次, 因此在第十步的时候事件可能在之后的流程中再次触发,触发的任务添加到microTasks 队列中,所有Js还会继续检测是否microTasks 中还有任务