资讯专栏INFORMATION COLUMN

FE.ES-理解Event Loop

longshengwang / 1295人阅读

摘要:新加了一个微任务和一个宏任务在当前执行栈的尾部下一次之前触发回调函数。阶段这个阶段主要执行一些系统操作带来的回调函数,如错误,如果尝试链接时出现错误,一些会把这个错误报告给。

JavaScript引擎又称为JavaScript解释器,是JavaScript解释为机器码的工具,分别运行在浏览器和Node中。而根据上下文的不同,Event loop也有不同的实现:其中Node使用了libuv库来实现Event loop; 而在浏览器中,html规范定义了Event loop,具体的实现则交给不同的厂商去完成。

浏览器中的Event Loops

根据2017年新版的HTML规范HTML Standard,浏览器包含2类事件循环:browsing contexts 和 web workers。

browsing contexts中有一个或多个Task Queue,即MacroTask Queue,仅有一个Job Queue,即MicroTask Queue。

macrotask queue(宏任务,不妨称为A

setTimeout

setInterval

setImmediate(node独有

requestAnimationFrame

I/O

UI rendering

microtask queue(微任务,不妨称为I

process.nextTick(node独有

Promises

Object.observe(废弃)

MutationObserver

这两个任务队列执行顺序:

取1个A中的task,执行之。

把所有I顺序执行完,再取A中的下一个任务。

为什么promise.then的回调比setTimeout先执行
代码开始执行时,所有这些代码在A中,形成一个执行栈(execution context stack),取出来执行之。
遇到setTimeout,则加到A中,遇到promise.then,则加到I中。
等整个执行栈执行完,取I中的任务。

(function test() {
    setTimeout(function() {console.log(4)}, 0);
    new Promise(function executor(resolve) {
        console.log(1);
        for( var i=0 ; i<10000 ; i++ ) {
            i == 9999 && resolve();
        }
        console.log(2);
    }).then(function() {
        console.log(5);
    });
    console.log(3);
})()
// 1
// 2
// 3
// 5
// 4
//浏览器渲染步骤:Structure(构建 DOM) ->Layout(排版)->Paint(绘制) 
//新的异步任务将在下一次被执行,因此就不会存在阻塞。
button.addEventListener("click", () => {
  setTimeout(fn, 0)
})

V8源码
https://github.com/v8/v8/blob...
https://github.com/v8/v8/blob...

NodeJS中的Event Loop

而在Node.js中,microtask会在事件循环的各个阶段之间执行,也就是一个阶段执行完毕,就会去执行microtask队列的任务。

node新加了一个微任务process.nextTick和一个宏任务setImmediate.

process.nextTick

在当前"执行栈"的尾部(下一次Event Loop之前)触发回调函数。也就是说,它指定的任务总是发生在所有异步任务之前。

process.nextTick(function A() {
  console.log(1);
  process.nextTick(function B(){console.log(2);});
});

setTimeout(function timeout() {
  console.log("TIMEOUT FIRED");
}, 0)
// 1
// 2
// TIMEOUT FIRED
setImmediate

setImmediate方法则是在当前"任务队列"的尾部添加事件,也就是说,它指定的任务总是在下一次Event Loop时执行,这与setTimeout(fn, 0)很像。

setImmediate(function A() {
  console.log(1);
  setImmediate(function B(){console.log(2);});
});

setTimeout(function timeout() {
  console.log("TIMEOUT FIRED");
}, 0);
//不确定

递归的调用process.nextTick()会导致I/O starving,官方推荐使用setImmediate()

process.nextTick(function foo() {
  process.nextTick(foo);
});
//FATAL ERROR: invalid table size Allocation failed - JavaScript heap out of memory

process.nextTick也会放入microtask quque,为什么优先级比promise.then高呢
在Node中,_tickCallback在每一次执行完TaskQueue中的一个任务后被调用,而这个_tickCallback中实质上干了两件事:

nextTickQueue中所有任务执行掉(长度最大1e4,Node版本v6.9.1)

第一步执行完后执行_runMicrotasks函数,执行microtask中的部分(promise.then注册的回调)所以很明显process.nextTick > promise.then”


node.js的特点是事件驱动,非阻塞单线程。当应用程序需要I/O操作的时候,线程并不会阻塞,而是把I/O操作交给底层库(LIBUV)。此时node线程会去处理其他任务,当底层库处理完I/O操作后,会将主动权交还给Node线程,所以Event Loop的用处是调度线程,例如:当底层库处理I/O操作后调度Node线程处理后续工作,所以虽然node是单线程,但是底层库处理操作依然是多线程。

根据Node.js官方介绍,每次事件循环都包含了6个阶段,对应到 libuv 源码中的实现,如下图所示

timers :这个阶段执行timer(setTimeout、setInterval)的回调
I/O callbacks:执行一些系统调用错误,比如网络通信的错误回调
idle, prepare :仅node内部使用
poll :获取新的I/O事件, 适当的条件下node将阻塞在这里
check :执行 setImmediate() 的回调
close callbacks :执行 socket 的 close 事件回调

timers阶段

timers 是事件循环的第一个阶段,Node 会去检查有无已过期的timer,如果有则把它的回调压入timer的任务队列中等待执行,事实上,Node 并不能保证timer在预设时间到了就会立即执行,因为Node对timer的过期检查不一定靠谱,它会受机器上其它运行程序影响,或者那个时间点主线程不空闲。但是把它们放到一个I/O回调里面,就一定是 setImmediate() 先执行,因为poll阶段后面就是check阶段。

I/O callbacks 阶段

这个阶段主要执行一些系统操作带来的回调函数,如 TCP 错误,如果 TCP 尝试链接时出现 ECONNREFUSED 错误 ,一些 *nix 会把这个错误报告给 Node.js。而这个错误报告会先进入队列中,然后在 I/O callbacks 阶段执行。

poll 阶段

poll 阶段主要有2个功能:

处理 poll 队列的事件

当有已超时的 timer,执行它的回调函数

even loop将同步执行poll队列里的回调,直到队列为空或执行的回调达到系统上限(上限具体多少未详),接下来even loop会去检查有无预设的setImmediate(),分两种情况:

若有预设的setImmediate(), event loop将结束poll阶段进入check阶段,并执行check阶段的任务队列

若没有预设的setImmediate(),event loop将阻塞在该阶段等待

注意一个细节,没有setImmediate()会导致event loop阻塞在poll阶段,这样之前设置的timer岂不是执行不了了?所以咧,在poll阶段event loop会有一个检查机制,检查timer队列是否为空,如果timer队列非空,event loop就开始下一轮事件循环,即重新进入到timer阶段。

check 阶段

setImmediate()的回调会被加入check队列中, 从event loop的阶段图可以知道,check阶段的执行顺序在poll阶段之后。

close 阶段

突然结束的事件的回调函数会在这里触发,如果 socket.destroy(),那么 close 会被触发在这个阶段,也有可能通过 process.nextTick() 来触发。

示例
setTimeout(()=>{
    console.log("timer1")

    Promise.resolve().then(function() {
        console.log("promise1")
    })
}, 0)

setTimeout(()=>{
    console.log("timer2")

    Promise.resolve().then(function() {
        console.log("promise2")
    })
}, 0)
/*浏览器中
timer1
promise1
timer2
promise2
*/
/*node中
timer1
timer2
promise1
promise2
*/
const fs = require("fs")

fs.readFile("test.txt", () => {
  console.log("readFile")
  setTimeout(() => {
    console.log("timeout")
  }, 0)
  setImmediate(() => {
    console.log("immediate")
  })
})
/*
readFile
immediate
timeout
*/

更多示例
libuv源码
https://github.com/libuv/libu...

其他 requestAnimationFrame

HTML5标准规定了setTimeout()的第二个参数的最小值(最短间隔),不得低于4毫秒,如果低于这个值,就会自动增加。在此之前,老版本的浏览器都将最短间隔设为10毫秒。另外,对于那些DOM的变动(尤其是涉及页面重新渲染的部分),通常不会立即执行,而是每16毫秒执行一次。这时使用requestAnimationFrame()的效果要好于setTimeout()

客户端可能实现了一个包含鼠标键盘事件的任务队列,还有其他的任务队列,而给鼠标键盘事件的任务队列更高优先级,例如75%的可能性执行它。这样就能保证流畅的交互性,而且别的任务也能执行到了。但是,同一个任务队列中的任务必须按先进先出的顺序执行。

用户点击与button.click()的区别:
用户点击:依次执行listener。浏览器并不实现知道有几个 listener,因此它发现一个执行一个,执行完了再看后面还有没有。
click:同步执行listener。 click方法会先采集有哪些 listener,再依次触发。
示例详情

参考资料
Promise的队列与setTimeout的队列有何关联?
浏览器的 Event Loop
Event Loops
深入理解js事件循环机制(Node.js篇)
JavaScript 运行机制详解:再谈Event Loop
Node.js 事件循环,定时器和 process.nextTick()

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

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

相关文章

  • JavaScript执行机制、事件循环

    摘要:曾经的理解首先,是单线程语言,也就意味着同一个时间只能做一件事,那么为什么不是多线程呢这样还能提高效率啊假定同时有两个线程,一个线程在某个节点上编辑了内容,而另一个线程删除了这个节点,这时浏览器就很懵逼了,到底以执行哪个操作呢所以,设计者把 Event Loop曾经的理解 首先,JS是单线程语言,也就意味着同一个时间只能做一件事,那么 为什么JavaScript不是多线程呢?这样还能提...

    rose 评论0 收藏0
  • 理解JS中的Event Loop机制

    摘要:前言前几天在理解的事件环机制中引发了我对浏览器里的好奇。接下来理解浏览器中的,先看一张图堆和栈堆是用户主动请求而划分出来的内存区域,比如你,就是将一个对象存入堆中,可以理解为存对象。废话不多说,直接上图个人理解。参考资料运行机制详解再谈 前言 前几天在理解node的事件环机制中引发了我对浏览器里Event Loop的好奇。我们都知道javascript是单线程的,任务是需要一个一个按顺...

    MASAILA 评论0 收藏0
  • 10分钟理解JS引擎的执行机制

    摘要:深入理解引擎的执行机制灵魂三问为什么是单线程的为什么需要异步单线程又是如何实现异步的呢中的中的说说首先请牢记点是单线程语言的是的执行机制。 深入理解JS引擎的执行机制 1.灵魂三问 : JS为什么是单线程的? 为什么需要异步? 单线程又是如何实现异步的呢? 2.JS中的event loop(1) 3.JS中的event loop(2) 4.说说setTimeout 首先,请牢记2...

    zzbo 评论0 收藏0
  • 理解浏览器和node.js中的Event loop事件循环

    摘要:浏览器和中并不一样,浏览器的是在中定义的规范,而中则由库实现。整个的这种运行机制又称为事件循环例子了解浏览器的后,查看下面例子,猜测浏览器是怎么输出的浏览器输出中的在内部有这样一个事件环机制。在启动时会初始化事件环。执行和中到期的。 大家都知道,javascript是一门单线程语言,因此为了实现主线程的不阻塞,Event Loop这样的方案应运而生。 浏览器和node中Event lo...

    iliyaku 评论0 收藏0
  • event loop 与 vue

    摘要:但是导致了很明显的性能问题。上述两个例子其实是在这个中找到的,第一个使用的版本是,这个版本的实现是采用了,而后因为的里的有,于是尤雨溪更改了实现,换成了,也就是后一个所使用的。后来尤雨溪了解到是将回调放入的队列。 结论 对于event loop 可以抽象成一段简单的代码表示 for (macroTask of macroTaskQueue) { // 1. Handle cur...

    springDevBird 评论0 收藏0

发表评论

0条评论

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