资讯专栏INFORMATION COLUMN

彻底搞懂浏览器Event-loop

source / 3065人阅读

摘要:检查宏任务队列,发现有的回调函数立即执行回调函数输出。接着遇到它的作用是在后将回调函数放到宏任务队列中这个任务在再下一次的事件循环中执行。

为什么会写这篇博文呢?

前段时间,和头条的小伙伴聊天问头条面试前端会问哪些问题,他称如果是他面试的话,event-loop肯定是要问的。那天聊了蛮多,event-loop算是给我留下了很深的印象,原因很简单,因为之前我从未深入了解过,如果是面试的时候,我遇到了这个问题,估计回答得肯定不如人意。

因此,最近我阅读了一些相关的文章,并细细梳理了一番,输出了本篇博文,希望能帮助大家搞懂浏览器的event-loop。后续会补充node中的event-loop。

1. 预备知识
JavaScript的运行机制:

(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。

(2)主线程之外,还存在"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。

(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。

(4)主线程不断重复上面的第三步

概括即是: 调用栈中的同步任务都执行完毕,栈内被清空了,就代表主线程空闲了,这个时候就会去任务队列中按照顺序读取一个任务放入到栈中执行。每次栈内被清空,都会去读取任务队列有没有任务,有就读取执行,一直循环读取-执行的操作

一个事件循环中有一个或者是多个任务队列
JavaScript中有两种异步任务:

宏任务: script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering

微任务: process.nextTick(Nodejs), Promises, Object.observe, MutationObserver;

2. 事件循环(event-loop)是什么?

主线程从"任务队列"中读取执行事件,这个过程是循环不断的,这个机制被称为事件循环。此机制具体如下:主线程会不断从任务队列中按顺序取任务执行,每执行完一个任务都会检查microtask队列是否为空(执行完一个任务的具体标志是函数执行栈为空),如果不为空则会一次性执行完所有microtask。然后再进入下一个循环去任务队列中取下一个任务执行。

详细说明:

选择当前要执行的宏任务队列,选择一个最先进入任务队列的宏任务,如果没有宏任务可以选择,则会跳转至microtask的执行步骤。

将事件循环的当前运行宏任务设置为已选择的宏任务。

运行宏任务。

将事件循环的当前运行任务设置为null。

将运行完的宏任务从宏任务队列中移除。

microtasks步骤:进入microtask检查点。

更新界面渲染。

返回第一步。

执行进入microtask检查的的具体步骤如下:

设置进入microtask检查点的标志为true。

当事件循环的微任务队列不为空时:选择一个最先进入microtask队列的microtask;设置事件循环的当前运行任务为已选择的microtask;运行microtask;设置事件循环的当前运行任务为null;将运行结束的microtask从microtask队列中移除。

对于相应事件循环的每个环境设置对象(environment settings object),通知它们哪些promise为rejected。

清理indexedDB的事务。

设置进入microtask检查点的标志为false。

需要注意的是:当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件。同一次事件循环中,微任务永远在宏任务之前执行。

图示:

3. Event-loop 是如何工作的?

先看一个简单的示例:

setTimeout(()=>{
    console.log("setTimeout1");
    Promise.resolve().then(data => {
        console.log(222);
    });
});
setTimeout(()=>{
    console.log("setTimeout2");
});
Promise.resolve().then(data=>{
    console.log(111);
});

思考一下, 运行结果是什么?

运行结果为:

111
setTimeout1
222
setTimeout2

我们来看一下为什么?

我们来详细说明一下, JS引擎是如何执行这段代码的:

主线程上没有需要执行的代码

接着遇到setTimeout 0,它的作用是在 0ms 后将回调函数放到宏任务队列中(这个任务在下一次的事件循环中执行)。

接着遇到setTimeout 0,它的作用是在 0ms 后将回调函数放到宏任务队列中(这个任务在再下一次的事件循环中执行)。

首先检查微任务队列, 即 microtask队列,发现此队列不为空,执行第一个promise的then回调,输出 "111"。

此时microtask队列为空,进入下一个事件循环, 检查宏任务队列,发现有 setTimeout的回调函数,立即执行回调函数输出 "setTimeout1",检查microtask 队列,发现队列不为空,执行promise的then回调,输出"222",microtask队列为空,进入下一个事件循环。

检查宏任务队列,发现有 setTimeout的回调函数, 立即执行回调函数输出"setTimeout2"。

再思考一下下面代码的执行顺序:

console.log("script start");

setTimeout(function () {
    console.log("setTimeout---0");
}, 0);

setTimeout(function () {
    console.log("setTimeout---200");
    setTimeout(function () {
        console.log("inner-setTimeout---0");
    });
    Promise.resolve().then(function () {
        console.log("promise5");
    });
}, 200);

Promise.resolve().then(function () {
    console.log("promise1");
}).then(function () {
    console.log("promise2");
});
Promise.resolve().then(function () {
    console.log("promise3");
});
console.log("script end");

思考一下,运行结果是什么?

运行结果为:

script start
script end
promise1
promise3
promise2
setTimeout---0
setTimeout---200
promise5
inner-setTimeout---0

那么为什么?

我们来详细说明一下,JS引擎是如何执行这段代码的:

首先顺序执行完主进程上的同步任务,第一句和最后一句的console.log

接着遇到setTimeout 0,它的作用是在 0ms 后将回调函数放到宏任务队列中(这个任务在下一次的事件循环中执行)。

接着遇到setTimeout 200,它的作用是在 200ms 后将回调函数放到宏任务队列中(这个任务在再下一次的事件循环中执行)。

同步任务执行完之后,首先检查微任务队列,即 microtask队列, 发现此队列不为空,执行第一个promise的then回调,输出 "promise1",然后执行第二个promise的then回调,输出"promise3",由于第一个promise的.then()的返回依然是promise,所以第二个.then()会放到microtask队列继续执行,输出 "promise2";

此时microtask队列为空,进入下一个事件循环,检查宏任务队列,发现有 setTimeout的回调函数,立即执行回调函数输出 "setTimeout---0",检查microtask 队列,队列为空,进入下一次事件循环.

检查宏任务队列,发现有 setTimeout的回调函数,立即执行回调函数输出"setTimeout---200".

接着遇到setTimeout 0,它的作用是在 0ms 后将回调函数放到宏任务队列中,检查微任务队列,即 microtask 队列, 发现此队列不为空,执行promise的then回调,输出"promise5"。

此时microtask队列为空,进入下一个事件循环,检查宏任务队列,发现有 setTimeout 的回调函数,立即执行回调函数输出,输出"inner-setTimeout---0".代码执行结束.

4. 为什么会需要event-loop?

因为 JavaScript 是单线程的。单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。为了协调事件(event),用户交互(user interaction),脚本(script),渲染(rendering),网络(networking)等,用户代理(user agent)必须使用事件循环(event loops)。

5. 参考文章:

https://segmentfault.com/a/11...

https://segmentfault.com/a/11...

https://segmentfault.com/a/11...

http://www.ruanyifeng.com/blo...

谢谢您花费宝贵的时间阅读本文,如果本文给了您一点帮助或者是启发,那么不要吝啬你的赞和Star哈,您的肯定是我前进的最大动力。https://github.com/YvetteLau/...

关注小姐姐的公众号,和小姐姐一起学前端。

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

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

相关文章

  • 最后一次搞懂 Event Loop

    摘要:由于是单线程的,这些方法就会按顺序被排列在一个单独的地方,这个地方就是所谓执行栈。事件队列每次仅执行一个任务,在该任务执行完毕之后,再执行下一个任务。 Event Loop 是 JavaScript 异步编程的核心思想,也是前端进阶必须跨越的一关。同时,它又是面试的必考点,特别是在 Promise 出现之后,各种各样的面试题层出不穷,花样百出。这篇文章从现实生活中的例子入手,让你彻底理解 E...

    gself 评论0 收藏0
  • 彻底搞懂 defer & async

    摘要:彻底搞懂通过浏览器的开发者工具可以直观的看到,图中蓝色的线和蓝色的字使用不同的表现形式表示这个事件触发的时间。当脚本下载完后立即执行,执行顺序不确定。 彻底搞懂 defer & async DOMContentLoaded showImg(https://segmentfault.com/img/remote/1460000013480394?w=1309&h=879); 通过 chr...

    luckyyulin 评论0 收藏0
  • 彻底搞懂JavaScript执行机制

    摘要:彻底搞懂执行机制首先我们大家都了解的是,是一门单线程语言,所以我们就可以得出是按照语句顺序执行的首先看这个显然大家都知道结果,依次输出,然而换一种这个时候再看代码的顺序执行,输出,,,。不过即使主线程为空,也是达不到的,根据标准,最低是。 彻底搞懂JavaScript执行机制 首先我们大家都了解的是,JavaScript 是一门单线程语言,所以我们就可以得出: JavaScript 是...

    hizengzeng 评论0 收藏0
  • 彻底搞懂路由跳转:location 和 history 接口

    摘要:在单页应用中,通常由前端来配置路由,根据不同的显示不同的内容。接口是新增的,它有五个方法可以改变而不刷新页面。事件能监听除和外的变化。而模式下,我们不仅要在事件回调里处理的变化,还需要分别在和方法里处理的变化。 在单页应用中,通常由前端来配置路由,根据不同的 url 显示不同的内容。想要知道这是如何做到的,首先得了解浏览器提供的两大 API: window.location lo...

    BWrong 评论0 收藏0

发表评论

0条评论

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