有关JavaScript事件循环的若干疑问探究

原因

虽然我完全没有系统地学习过事件循环机制,但是经过一段时间的体验,听说过一些比如宏任务和微任务,单线程,Ajax和它是异步操作,会被执行最后和其他零散的信息。结合实际的代码,大部分情况下也可以保证代码按照我想要的顺序执行,但是当我实际被问到这个问题的时候,我发现我并不能真正理解其中的原理,有很多相关资料,但还是得用自己的理解来表达。

为什么要有事件循环?

首先是一个简单的问题,换句话说,事件循环是做什么的,我为什么要学习这个?正如第一段所说,众所周知它是单线程语言,但这并不意味着不需要异步操作。反过来想。如果你写的所有Ajax操作都是同步的会发生什么:每次我们下一次向服务器发送请求时,整个页面都会停滞不前,直到请求返回,不管响应时间是1毫秒,1秒,或 1 分钟。对于用户体验来说,这无疑是一场灾难,所以提供了各种异步编程方法:事件循环、、、等。这里我们还是先关注事件循环,随着问题的深入,我们就会知道事件循环有什么问题为我们解决。

事件循环是如何工作的?

要了解这个问题,建议先看这个视频:Event Loop到底是什么? ,然后是视频中提到的网站:放大镜,结合视频,我们可以直观的看到事件是如何循环的,网站根据输入的代码来动画化这个过程。

按照视频的思路,我们将执行分为几个部分:调用栈、事件循环、回调队列、其他api。

调用栈

因为它是单线程的,所以我们只能逐句执行我们的代码。编译器每次读取一个函数,就将其压入栈中,栈顶函数返回结果时,弹出栈。在这个过程中,只有同步函数函数会进入调用栈跟随正常的执行流程,异步函数会进入回调队列,形成事件循环的第一步。

网络 API

视频中最让人意外的是,很多我们熟悉的功能都没有提供,而是来自于Web API,比如Ajax、DOM等。方法的实现并没有出现在源码中V8的,因为是浏览器提供的,更准确的说应该是运行环境提供的,因为运行环境不统一,不同的浏览器内核也没有提到。现在,我们可以将其分为浏览器和节点。似乎和我们讨论的事件循环无关,但还是有区别的。我们稍后会解释这个问题。

任务队列

异步方法经过Web API处理后会进入任务队列。例如,浏览器提供了一个计时器。处理此方法时,计时器在后台启动。当达到设定的时间时,该方法将被添加到任务队列中。当这批同步任务处理完毕后,方法会从队列中取出,放入调用栈中执行。因此,我们实际设置的时间是指最早可以执行该方法的时间,而不是延迟执行多长时间。让我们看一个例子,你可以先在脑海中运行模拟:

console.log('1')
setTimeout(function setFirstTimeout() {
  console.log('2')
  new Promise(function (resolve) {
    console.log('3')
    resolve()
  }).then(function () {
    console.log('4')

图片[1]-有关JavaScript事件循环的若干疑问探究-唐朝资源网

}) },0) new Promise(function (resolve) { console.log('5') resolve() }).then(function () { console.log('6') }) console.log('7')

实际执行我们可以得到 1、5、 7、6、2、3、4 结果,把这段代码放到提到的网站上上面可以很清楚的看到流程,我们定义这个方法经过Web API处理后进入Queue,等待主线程的代码执行完毕,然后通过事件循环的机制进入调用栈。

图片[2]-有关JavaScript事件循环的若干疑问探究-唐朝资源网

这一切都说得通:为什么总是最后执行,但真的是这样吗?让我们看下一个问题。

是否必须在所有代码的末尾执行 – 宏任务和微任务

即使这个问题没有仔细研究过,但我从经验中知道肯定不是这样的,虽然会相对延迟。 ,但并不总是在所有代码的末尾执行,这里有一个更大的问题——宏任务和微任务。我们在上面的代码中添加了一个 DOM 操作。

console.log('1')
$.on('button','click',function onClick(){
    console.log('Clicked');
})

图片[3]-有关JavaScript事件循环的若干疑问探究-唐朝资源网

setTimeout(function setFirstTimeout() { console.log('2') new Promise(function (resolve) { console.log('3') resolve() }).then(function () { console.log('4') }) },0) new Promise(function (resolve) { console.log('5') resolve() }).then(function () { console.log('6') }) console.log('7')

直接看结果,当事件的回调方法进入事件队列时,我点击了绑定了事件的按钮,所以点击的回调方法也进入了事件队列中,同步任务处理的时候,按照队列先进先出的原则,会先处理事件队列的回调方法,再处理点击事件的回调方法。

图片[4]-有关JavaScript事件循环的若干疑问探究-唐朝资源网

图片[5]-有关JavaScript事件循环的若干疑问探究-唐朝资源网

这不是一个巧妙的例子,但 DOM 操作确实与宏任务属于同一类别。与宏观任务相比,微任务是常见的类别。如下:

宏任务

微任务

其实从上面的例子中,应该有人已经发现执行顺序不正常了。 then 中的回调函数既不执行也不进入回调队列。显然,程序中没有错误。正是因为宏任务与微任务不同。

图片[6]-有关JavaScript事件循环的若干疑问探究-唐朝资源网

简单来说,宏任务和微任务都有自己的任务队列。在微任务队列中,当前宏任务执行完毕后,依次执行微任务。让我们丰富前面的例子:

console.log("1");
setTimeout(function s1() {
  console.log("2");
  process.nextTick(function p2() {
    console.log("3");
  });
  new Promise(function (resolve) {
    console.log("4");
    resolve();
  }).then(function t2() {
    console.log("5");
  });
});
process.nextTick(function p1() {

图片[7]-有关JavaScript事件循环的若干疑问探究-唐朝资源网

console.log("6"); }); new Promise(function (resolve) { console.log("7"); resolve(); }).then(function t1() { console.log("8"); }); console.log("9"); setTimeout(function s2() { console.log("10"); process.nextTick(function () { console.log("11"); }); new Promise(function (resolve) { console.log("12"); resolve(); }).then(function () { console.log("13"); });

图片[8]-有关JavaScript事件循环的若干疑问探究-唐朝资源网

});

在v16版本节点环境的执行结果为:1、7、9、6、8、2、4、@ >3、5、@ >15、1@>12、11、13,其他环境会有差异,后面再讲,先看手头的问题,以微任务为前提进行分析。

执行.log(5、4@>遇到宏任务,加入Queue,遇到微任务。加入Task Queue,执行.log(5、5@>微任务加入Task Queue for .log(5、6@>遇到宏任务时,添加到Queue中

全局宏任务执行后,我们可以得到这样两个队列,而1、@的输出7、9,接下来按照规则执行这个宏任务中的微任务p1和t1 , 得到 6 和 8。

队列

s1

p1

s2

t1

继续下一个宏任务s1:

.log(5、9@>遇到微任务时,将其加入Task Queue并执行.log in new(4)将微任务加入Task Queue

p2

t2

因此,以下输出为:2、4、@>3、5,以此类推,以下规则类似,不再一一赘述。

Node和浏览器有什么区别?

之前的问题应该解决了,但也带来了新的问题。前面提到过使用v16版本的node环境。执行,那么如果不是v16版本的node,甚至不使用node运行,会是什么结果呢?这次看到这篇文章评论区的一些讨论,彻底了解了执行机制,v10之前的事件循环中node的处理与浏览器不同,所以又得到了一个结果。切换到v10版本后,还是得到1、7、9、6、8、2、4、@>3、@ >5、15、1@>12、11、13,我个人认为以最新版本为准有兴趣的可以看文章评论区。

然后还有另一种情况。一开始我在Vue中验证了这段代码,得到的结果是1、7、9、8、2、4、@>5、 6、15、1@>12、13、3、11,前提是.是一个宏任务,这个结果是正确的,但我不太清楚为什么。另外,我以为还有一种方法,我查了一下,发现是不同的话题。限于篇幅,打算再开篇学习。具体内容也可以看这篇博客。 Vue的细节是还是?

还有什么?

写这篇博客是为了了解事件循环的机制,没想到内容这么多。刚上班的时候遇到一个问题,就是定时器走的越来越快。那个时候,我以为我已经想通了。今天从这篇文章的角度回过头来看,我只看到了冰山一角,而这篇文章也只写了事件循环的冰山一角。幸好现在知道了,除了Vue的问题,还有一个和事件循环相关的渲染问题,这部分内容后面会整理成文章,这里有一篇博文和一段视频推荐:

深入解析你所不知道的和浏览器渲染、帧动画、空闲回调(电影演示)

在循环中

© 版权声明
THE END
喜欢就支持一下吧
点赞6赞赏 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容