Event Loop 事件循环机制 详解(一)

我们都知道,javascript 是单线程的,其通过使用异步而不阻塞主进程执行。

单线程意味着,javascript代码在执行的任何时候,都只有一个主线程来处理所有的任务。

而非阻塞则是当代码需要进行一项异步任务(无法立刻返回结果,需要花一定时间才能返回的任务,如I/O事件)的时候,主线程会挂起(pending)这个任务,然后在异步任务返回结果的时候再根据一定规则去执行相应的回调。

单线程是必要的,也是javascript这门语言的基石,原因之一在其最初也是最主要的执行环境——浏览器中,我们需要进行各种各样的dom操作。试想一下 如果javascript是多线程的,那么当两个线程同时对dom进行一项操作,例如一个向其添加事件,而另一个删除了这个dom,此时该如何处理呢?因此,为了保证不会 发生类似于这个例子中的情景,javascript选择只用一个主线程来执行代码,这样就保证了程序执行的一致性。

浏览器环境下,会维护一个任务队列,当异步任务到达的时候加入队列,等待事件循环到合适的时机执行,即事件的循环机制

实际上,js 引擎并不只维护一个任务队列,总共有两种任务

Task(macroTask): 
setTimeout, setInterval, setImmediate, I/O, UI rendering

microTask:
Promise, process.nextTick, Object.observe, MutationObserver, MutaionObserver
看如下代码:
$(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 中还有任务

 

 

posted @ 2019-11-02 17:40  To_Ashen  阅读(237)  评论(0编辑  收藏  举报