浏览器的事件环机制

今天我们来了解一下浏览器的事件环机制。

浏览器模型

  • User Interface(用户界面)-包括地址栏、前进/后退按钮、书签菜单等
  • Browser engine(浏览器引擎)-在用户界面和呈现引擎之间传送指令
  • Rendering engine(呈现引擎)-又称渲染引擎,也被称为浏览器内核,在线程方面又称为UI线程
  • Networking(网络)-用于网络调用,比如 HTTP 请求
  • UI Backend(用户界面后端)-用于绘制基本的窗口小部件(UI线程)
  • JavaScript解释器-用于解析和执行 JavaScript 代码(JS线程)
  • Data Persistence(数据存储)-这是持久层。浏览器需要在硬盘上保存各种数据,例如 Cookie

注意:UI线程和JS线程是互斥的,因为它们两个共用一个线程,即主线程。

JS是单线程的

为什么JS要设计成单线程的?这是由Javascript这门脚本语言的用途决定的。JS的主要工作是操作DOM,如果设计成多线程的,两个线程对同一个元素进行操作,浏览器就不知道该如何处理了。单线程的好处是,我操作的时候你等着,等我操作完成后,你再进行操作,避免冲突。

Philip Roberts的演讲图片

这张图相信大家在很多文章中都见过。

对于上图我找到一个更容易理解的,如下图:

图片来源:segmentfault.com/a/119000001…

对图片的解释

主线程运行的时候,产生堆(heap)和栈(stack)。在对一个调用栈中的代码进行操作的时候,其他的都要等着。在操作过程中遇到一些类似于setTimeout等异步操作的时候,会交给浏览器的其他模块进行处理。在这些异步操作达到特定条件(定时器等待指定时间之后,ajax请求返回数据)时,把相应的回调函数放入指定的“任务队列”。只要栈中的代码执行完毕,主线程就会去读取"任务队列",依次执行那些事件所对应的回调函数。

任务队列

大家可以看到图片中写的:宏任务队列和微任务队列。如果大家想详细了解可以看下规范。其中微任务要早于宏任务执行。

常见任务队列

  • 宏任务:script(全局任务), setTimeout, setInterval, setImmediate, I/O, UI rendering, MessageChannel
  • 微任务:Promise, Object.observer, MutationObserver.

测试代码

在html页面写如下代码:

<script>
    console.log(1);
    setTimeout(function () {
        console.log("4");
    }, 0);
    Promise.resolve().then(() => {
        console.log("3");
    });
    console.log(2);
</script>
<script src="out.js"></script>
<script>
    console.log("a");
    Promise.resolve().then(() => {
        console.log("c");
    });
    setTimeout(function () {
        console.log("d");
    }, 0);
    console.log("b");
</script>

<script>
    console.log("end");
</script>
复制代码

其中 out.js代码如下:

console.log("out1");
setTimeout(function () {
    console.log("out4");
}, 0);
Promise.resolve().then(() => {
    console.log("out3");
});
console.log("out2");
复制代码

输出结果:

  • 1
  • 2
  • 3
  • out1
  • out2
  • out3
  • a
  • b
  • c
  • end
  • 4
  • out4
  • d

根据代码得出结论

前提:script(全局任务)是宏任务 分析:当主线程遇到上面代码时,会把所有的script标签以及外部的js文件放入宏任务队列中(先后顺序就是书写的顺序)。此时主任务队列中没有可执行的代码。所以就取宏任务队列中的第一个宏任务

console.log(1);
setTimeout(function () {
    console.log("4");
}, 0);
Promise.resolve().then(() => {
    console.log("3");
});
console.log(2);
复制代码

先输出 1;遇到setTimeout交给其它模块执行,在到达指定时间(10毫秒或16毫秒)之后会把回调函数放到宏任务队列最后;遇到Promise同样交给其它模块执行,达到条件之后放到微任务队列;再输出 2。此时宏任务队列该执行 out.js文件了,但是微任务队列中已经有微任务在排队了(Promise.resolve().then()中的回调函数)。微任务要早于宏任务执行,所以要先输出 3。再去执行out.js中的代码。out.js中的代码以及后面script标签内的代码和上面代码类似,就不再一一赘述。所以当输出 c 之后。栈(stack)和微任务队列中已没有可以执行的代码。剩下的是宏任务队列中的代码:依次是

<script>
    console.log("end");
</script>

function () {
    console.log("4");
}

function () {
    console.log("out4");
}

function () {
    console.log("d");
}
复制代码

然后依次放到主栈中执行,输出:end 4 out4 d 其它宏任务和微任务都遵循这个规则,就不一一举例了。

最后做一下总结:

  • 浏览器中的宏任务:script(全局任务), setTimeout, setInterval, setImmediate, I/O, UI rendering, MessageChannel

  • 浏览器中的微任务:Promise, Object.observer, MutationObserver.

  • 浏览器的事件环机制:

    • 1.所有同步任务都在主线程上执行,形成一个执行栈;主线程之外,还存在一个任务队列。只要异步任务有了运行结果,就在任务队列之中放置一个事件;
    • 2.执行栈执行过程中,遇到异步操作就交给其他模块处理,只要异步任务有了运行结果,就在任务队列之中放置一个事件(宏任务放到宏任务队列,微任务放到微任务队列);
    • 3.一旦执行栈中的所有同步任务执行完毕,系统就会依次读取微任务队列中的全部微任务放到主栈中执行(执行微任务的时候执行第2步);
    • 4.清空微任务队列之后,读取一个宏任务,放到主栈中执行(执行宏任务的时候执行第2步)。执行完毕后再去清空微任务队列中的微任务。。。
    • 5.主线程不断重复上面的第3、4步。

参考链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值