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中实质上干了两件事:

  1. nextTickQueue中所有任务执行掉(长度最大1e4,Node版本v6.9.1)
  2. 第一步执行完后执行_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(),分两种情况:

  1. 若有预设的setImmediate(), event loop将结束poll阶段进入check阶段,并执行check阶段的任务队列
  2. 若没有预设的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()

FE.ES-理解Event Loop相关推荐

  1. JavaScript中事件循环的理解 Event Loop

    为了解决单线程运行阻塞问题,JavaScript用到了计算机系统的一种运行机制,这种机制就叫做事件循环(Event Loop) 事件循环(Event Loop) 在JavaScript中,所有的任务都 ...

  2. 【js进阶】全面理解Event Loop这一篇就够了

    文章目录 一.前言 二.Event Loop知识铺垫 1.微任务(MircoTask) 2.宏任务(MacroTask/Task) 3.javascript runtime 三.浏览器环境的Event ...

  3. 深入理解 Event Loop

    众所周知,JavaScript(以下简称 JS) 是单线程语言,在 html5 中增加了 web workers,web workers 是新开了线程执行的,那么 JS 还是单线程的吗?当然是,为什么 ...

  4. 跟着 Event loop 规范理解浏览器中的异步机制

    原文发自我的 GitHub blog,欢迎关注 前言 我们都知道 JavaScript 是一门单线程语言,这意味着同一事件只能执行一个任务,结束了才能去执行下一个.如果前面的任务没有执行完,后面的任务 ...

  5. 理解node.js中的 Event Loop

    node中的 "event loop" 正是node能处理高并发的核心所在.也正是因为它,node虽然在本质上是个单线程,却能让大量的操作处于后台运行.这篇文章将详细说明 even ...

  6. JavaScript 运行机制详解:Event Loop

    转自: http://www.ruanyifeng.com/blog/2014/10/event-loop.html 一.为什么JavaScript是单线程? JavaScript语言的一大特点就是单 ...

  7. 【朴灵评注】JavaScript 运行机制详解:再谈Event Loop

    PS: 我先旁观下大师们的讨论,得多看书了~ 别人说的:"看了一下不觉得评注对到哪里去,只有吹毛求疵之感. 比如同步异步介绍,本来就无大错:比如node图里面的OS operation,推敲 ...

  8. JavaScript 运行机制详解:再谈Event Loop

    原文地址:http://www.ruanyifeng.com/blog/2014/10/event-loop.html 一年前,我写了一篇<什么是 Event Loop?>,谈了我对Eve ...

  9. Js Event Loop

    Js Event Loop 本文主要介绍 Node 中的事件循环 参考 官网 (什么是 Event Loop? - 阮一峰的网络日志 (ruanyifeng.com)) What is the Eve ...

最新文章

  1. python使用方法视频-Python读取视频的两种方法(imageio和cv2)
  2. 推荐 15 款常用开发工具
  3. 123_Power PivotPower BI DAX函数说明速查
  4. java获取请求的url地址
  5. 大数据有哪些特点和作用
  6. OpenStack云计算快速入门之三:OpenStack镜像管理
  7. 近年来最流行网络词汇及论坛用语
  8. ActiveReports 9 新功能:创新的报表分层设计理念
  9. ae批量修改字体_AE脚本pt_TextEdit v2.5批量修改文字图层样式字体大小属性编辑工具...
  10. 51单片机实训(一)————Keil 基本操作
  11. 【转】CT (电子计算机断层扫描)
  12. 织梦模板建站必须学会的基本代码
  13. 如何修改PDF内容,PDF怎么旋转页面
  14. “贵州与您相约”英文网站8月18日正式上线;大华股份助力长兴打造景区数字化管理样板 | 全球旅报...
  15. SCOI2014 方伯伯的玉米田 题解
  16. Failure to find xxx in http://maven.aliyun.com/nexus/content/groups/public
  17. 新版Chrome自动禁用第三方插件的解决办法[转]
  18. Seata源码走读分析
  19. 怎么退出用户登录linux,linux如何退出用户
  20. sap 标准委外和工序委外_SAP PP 工序委外详解

热门文章

  1. Django之form组件加cookie,session
  2. Android源码分析(一)-----如何快速掌握Android编译文件
  3. bzoj 1015 [JSOI2008]星球大战starwar
  4. 桥接模式和php实现
  5. iOS-推送通知详解
  6. SharePoint 2013 Ajax 造成页面无法编辑
  7. 访问被拒绝:“Interop.jmail”
  8. stl之set集合容器应用基础
  9. 简要说明php数组的类型,php数组的概述及分类与声明代码演示
  10. python变量命名规则思维导图_python基础知识点思维导图