资讯专栏INFORMATION COLUMN

Javascript事件循环机制以及渲染引擎何时渲染UI

cnio / 454人阅读

摘要:的一大特点就是单线程,而这个线程中拥有唯一的一个事件循环。事件循环基本概念代码的执行过程中,除了依靠函数调用栈来搞定函数的执行顺序外,还依靠任务队列来搞定另外一些代码的执行。之后全局上下文进入函数调用栈。

JavaScript的一大特点就是单线程,而这个线程中拥有唯一的一个事件循环。
事件循环基本概念

JavaScript代码的执行过程中,除了依靠函数调用栈来搞定函数的执行顺序外,还依靠任务队列(task queue)来搞定另外一些代码的执行。

一个线程中,事件循环是唯一的,但是任务队列可以拥有多个。

任务队列又分为macro-task(宏任务)与micro-task(微任务),在最新标准中,它们被分别称为task与jobs。

macro-task大概包括:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。

micro-task大概包括: process.nextTick, Promise, Object.observe(已废弃), MutationObserver(html5新特性)

setTimeout/Promise等我们称之为任务源。而进入任务队列的是他们指定的具体执行任务。

// setTimeout中的回调函数才是进入任务队列的任务
setTimeout(function() {
    console.log("xxxx");
})
// 非常多的同学对于setTimeout的理解存在偏差。所以大概说一下误解:
// setTimeout作为一个任务分发器,这个函数会立即执行,而它所要分发的任务,也就是它的第一个参数,才是延迟执行

来自不同任务源的任务会进入到不同的任务队列。其中setTimeout与setInterval是同源的。

其中每一个任务的执行,无论是macro-task还是micro-task,都是借助函数调用栈来完成。

事件循环执行循序

事件循环的顺序,决定了JavaScript代码的执行顺序。它从script(整体代码)开始第一次循环。之后全局上下文进入函数调用栈。直到调用栈清空(只剩全局),然后执行所有的micro-task。当所有可执行的micro-task执行完毕之后,本轮循环结束。下一轮循环再次从macro-task开始,找到其中一个任务队列执行完毕,然后再执行所有的micro-task,这样一直循环下去。

当我们在执行setTimeout任务中遇到setTimeout时,它仍然会将对应的任务分发到setTimeout队列中去,但是该任务就得等到下一轮事件循环执行。

那么整个事件循环中何时进行ui render呢?
begin
setTimeout(function() {
    // 应该是这里执行前开始渲染ui,试试用alert阻塞下。
    alert(" ui 已经渲染完毕了吗? ");
    console.log("timeout1");
})

new Promise(function(resolve) {
    console.log("promise1");
    for(var i = 0; i < 1000; i++) {
        i == 99 && resolve();
    }
    console.log("promise2");
}).then(function() {
    console.log("then1");
    alert(" ui 开始渲染 ");
})

console.log("global1");

div.innerHTML = "end";

上述代码中修改了div的内容,那么在执行那句js代码之后渲染引擎开始修改div的内容呢?

根据HTML Standard,一轮事件循环执行结束之后,下轮事件循环执行之前开始进行UI render。即:macro-task任务执行完毕,接着执行完所有的micro-task任务后,此时本轮循环结束,开始执行UI render。UI render完毕之后接着下一轮循环。

在chrome浏览器中执行以上代码,控制台先输出promise1,promise2,global1,then1(micro-task任务输出),弹出"ui 开始渲染"警告框,点击确定之后,页面中的"begin"变为"end",再弹出警告框"ui 已经渲染完毕了吗?" ,点击确认之后再输入timeout1.

再来一个稍微复杂一点的例子
1
begin
// Let"s get hold of those elements
var outer = document.querySelector(".outer");
var inner = document.querySelector(".inner");

var i = 0;

// Let"s listen for attribute changes on the
// outer element
new MutationObserver(function() {
    console.log("mutate");
}).observe(outer, {
    attributes: true
});

// Here"s a click listener…
function onClick() {
    i++;

    if(i === 1) {
        inner.innerHTML = "end";
    }

    console.log("click");

    setTimeout(function() {
        alert("锚点");
        console.log("timeout");
    }, 0);

    Promise.resolve().then(function() {
        console.log("promise");
    });


    outer.setAttribute("data-random", Math.random());
}

// …which we"ll attach to both elements
inner.addEventListener("click", onClick);
outer.addEventListener("click", onClick);

当我们点击 inner div 时程序依次的执行顺序是:

onclick 入 JS stack

打印出 click

将 timeout 压入到 macrotask

将 promise 压入到 microtask

修改 outer 属性 data-random

将 mutate 压入到 microtask,

onclick 出 JS stack

此时,由于用户点击事件onclick产生的macrotask执行完毕,JS stack 清空,开始执行microtask.

promise 入 JS stack

打印出 promise

promise 出 JS stack

mutate 入 JS stack

打印出 mutate

mutate 出 JS stack

此时,microtask 执行完毕,JS stack 清空,但是由于事件冒泡,接着执行outer上的onclick事件.

onclick 入 JS stack

打印出 click

将 timeout 压入到 macrotask

将 promise 压入到 microtask

修改 outer 属性 data-random

将 mutate 压入到 microtask,

onclick 出 JS stack

此时,由于outer上的onclick事件产生的macrotask执行完毕,JS stack 清空,开始执行microtask.

promise 入 JS stack

打印出 promise

promise 出 JS stack

mutate 入 JS stack

打印出 mutate

mutate 出 JS stack

此时,本轮事件循环结束,UI 开始 render.

页面中inner的innerHTML变为end

此时,UI render 完毕,开始下一轮事件循环.

timeout 入 JS stack

弹出警告 锚点.

打印出 timeout

timeout 出 JS stack

timeout 入 JS stack

弹出警告 锚点.

打印出 timeout

timeout 出 JS stack

到此为止,整个事件执行完毕,我们可以看到在弹出警告框之前inner的内容已经改变

那如果不是用户点击事件触发onclick,而是js触发呢?
inner.addEventListener("click", onClick);
outer.addEventListener("click", onClick);
inner.click();

此时的执行顺序是:

首先是script(整体代码)入 JS stack

onclick 入 JS stack

打印出 click

将 timeout 压入到 macrotask

将 promise 压入到 microtask

修改 outer 属性 data-random

将 mutate 压入到 microtask,

onclick 出 JS stack

此时,inner 的 onclick 已经出 JS stack,但是script(整体代码)还没有出 JS stack,还不能执行microtask,由于冒泡,接着执行 outer 的 onclick.

onclick 入 JS stack

打印出 click

将 timeout 压入到 macrotask

将 promise 压入到 microtask

修改 outer 属性 data-random

接着执行的outer.setAttribute("data-random", Math.random());,但是由于上一个mutation microtask还处于等待状态,不能再添加mutation microtask,所以这里不会将 mutate 压入到 microtask。接着执行:

onclick 出 JS stack

script(整体代码)出 JS stack

此时,inner.click()执行完毕,script(整体代码)已出 JS stack,JS stack 清空,开始执行mircotask.

promise 入 JS stack

打印出 promise

promise 出 JS stack

mutate 入 JS stack

打印出 mutate

mutate 出 JS stack

promise 入 JS stack

打印出 promise

promise 出 JS stack

此时,所有的mircotask执行完毕,本轮事件循环结束,UI 开始 render.

页面中inner的innerHTML变为end

此时,UI render 完毕,开始下一轮事件循环.

timeout 入 JS stack

弹出警告 锚点.

打印出 timeout

timeout 出 JS stack

timeout 入 JS stack

弹出警告 锚点.

打印出 timeout

timeout 出 JS stack

到此为止,整个事件执行完毕,我们可以看到在弹出警告框之前inner的内容已经改变

总结:首先执行macrotask,当js stack为空时执行microtask,接着开始UI render,接着再开始下一轮循环

参考文献:
深入核心,详解事件循环机制
Tasks, microtasks, queues and schedules

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/107342.html

相关文章

  • 关于vue中next和Tick(nextTick)的一点理解

    摘要:为什么叫按照官网的解释在下次更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的。在下个事件循环执行时确实是最新的了,但是回调并没有在下个事件循环执行。 前言 在这之前我是没有怎么看过vue源码的,但是看了源码后又产生了一些疑问,如果不看源码我还真没有任何疑问的去用nextTick,因为我只知道我想获取更新后的dom我就在里面写回调,只管写准没错,有天好奇调试了下...

    mgckid 评论0 收藏0
  • 事件循环机制和task执行顺序的一些概括(javascript)

    摘要:而当响应成功了以后,浏览器的事件表则会将回调函数添加至事件队列中等待执行。事件循环器会不停的检查事件队列,如果不为空,则取出队首压入执行栈执行。类型的任务目前包括了以及的回调函数。 事件循环(event loop) : 首先说事件队列(task queue) 事件队列是一个存储着待执行任务的队列,其中的任务严格按照时间先后顺序执行,排在队头的任务将会率先执行,而排在队尾的任务会最后执行...

    未东兴 评论0 收藏0
  • 事件循环机制

    摘要:事件触发线程主要负责将准备好的事件交给引擎线程执行。进程浏览器渲染进程浏览器内核,主要负责页面的渲染执行以及事件的循环。第二轮循环结束。 将自己读到的比较好的文章分享出来,大家互相学习,各位大佬有好的文章也可以留个链接互相学习,万分感谢! 线程与进程 关于线程与进程的关系可以用下面的图进行说明: showImg(https://segmentfault.com/img/bVbjSZt?...

    Blackjun 评论0 收藏0
  • 事件循环机制

    摘要:事件触发线程主要负责将准备好的事件交给引擎线程执行。进程浏览器渲染进程浏览器内核,主要负责页面的渲染执行以及事件的循环。第二轮循环结束。 将自己读到的比较好的文章分享出来,大家互相学习,各位大佬有好的文章也可以留个链接互相学习,万分感谢! 线程与进程 关于线程与进程的关系可以用下面的图进行说明: showImg(https://segmentfault.com/img/bVbjSZt?...

    CloudwiseAPM 评论0 收藏0

发表评论

0条评论

cnio

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<