Fundebug经做者浪里行舟受权首发,未经赞成请勿转载。javascript
本文咱们将会介绍 JS 实现异步的原理,而且了解了在浏览器和 Node 中 Event Loop 实际上是不相同的。html
咱们常常说 JS 是单线程执行的,指的是一个进程里只有一个主线程,那到底什么是线程?什么是进程?前端
官方的说法是:进程是 CPU 资源分配的最小单位;线程是 CPU 调度的最小单位。这两句话并很差理解,咱们先来看张图:html5
以 Chrome 浏览器中为例,当你打开一个 Tab 页时,其实就是建立了一个进程,一个进程中能够有多个线程(下文会详细介绍),好比渲染线程、JS 引擎线程、HTTP 请求线程等等。当你发起一个请求时,其实就是建立了一个线程,当请求结束后,该线程可能就会被销毁。java
简单来讲浏览器内核是经过取得页面内容、整理信息(应用 CSS)、计算和组合最终输出可视化的图像结果,一般也被称为渲染引擎。node
浏览器内核是多线程,在内核控制下各线程相互配合以保持同步,一个浏览器一般由如下常驻线程组成:ios
好比 setTimeout 定时器计数结束, ajax 等异步请求成功并触发回调函数,或者用户触发点击事件时,该线程会将整装待发的事件依次加入到任务队列的队尾,等待 JS 引擎线程的执行。git
事件循环中的异步队列有两种:macro(宏任务)队列和 micro(微任务)队列。宏任务队列能够有多个,微任务队列只有一个。github
一个完整的 Event Loop 过程,能够归纳为如下阶段:web
咱们总结一下,每一次循环都是一个这样的过程:
当某个宏任务执行完后,会查看是否有微任务队列。若是有,先执行微任务队列中的全部任务,若是没有,会读取宏任务队列中排在最前的任务,执行宏任务的过程当中,遇到微任务,依次加入微任务队列。栈空后,再次读取微任务队列里的任务,依次类推。
接下来咱们看道例子来介绍上面流程:
Promise.resolve().then(()=>{ console.log('Promise1') setTimeout(()=>{ console.log('setTimeout2') },0) }) setTimeout(()=>{ console.log('setTimeout1') Promise.resolve().then(()=>{ console.log('Promise2') }) },0)
最后输出结果是 Promise1,setTimeout1,Promise2,setTimeout2
Node 中的 Event Loop 和浏览器中的是彻底不相同的东西。Node.js 采用 V8 做为 js 的解析引擎,而 I/O 处理方面使用了本身设计的 libuv,libuv 是一个基于事件驱动的跨平台抽象层,封装了不一样操做系统一些底层特性,对外提供统一的 API,事件循环机制也是它里面的实现(下文会详细介绍)。
Node.js 的运行机制以下:
其中 libuv 引擎中的事件循环分为 6 个阶段,它们会按照顺序反复运行。每当进入某一个阶段的时候,都会从对应的回调队列中取出函数去执行。当队列为空或者执行的回调函数数量到达系统设定的阈值,就会进入下一阶段。
从上图中,大体看出 node 中的事件循环的顺序:
外部输入数据-->轮询阶段(poll)-->检查阶段(check)-->关闭事件回调阶段(close callback)-->定时器检测阶段(timer)-->I/O 事件回调阶段(I/O callbacks)-->闲置阶段(idle, prepare)-->轮询阶段(按照该顺序反复运行)...
注意:上面六个阶段都不包括 process.nextTick()(下文会介绍)
接下去咱们详细介绍timers
、poll
、check
这 3 个阶段,由于平常开发中的绝大部分异步任务都是在这 3 个阶段处理的。
(1) timer
timers 阶段会执行 setTimeout 和 setInterval 回调,而且是由 poll 阶段控制的。
一样,在 Node 中定时器指定的时间也不是准确时间,只能是尽快执行。
(2) poll
poll 是一个相当重要的阶段,这一阶段中,系统会作两件事情
而且在进入该阶段时若是没有设定了 timer 的话,会发生如下两件事情
若是 poll 队列为空时,会有两件事发生
固然设定了 timer 的话且 poll 队列为空,则会判断是否有 timer 超时,若是有的话会回到 timer 阶段执行回调。
(3) check 阶段
setImmediate()的回调会被加入 check 队列中,从 event loop 的阶段图能够知道,check 阶段的执行顺序在 poll 阶段以后。
咱们先来看个例子:
console.log('start') setTimeout(() => { console.log('timer1') Promise.resolve().then(function() { console.log('promise1') }) }, 0) setTimeout(() => { console.log('timer2') Promise.resolve().then(function() { console.log('promise2') }) }, 0) Promise.resolve().then(function() { console.log('promise3') }) console.log('end') //start=>end=>promise3=>timer1=>timer2=>promise1=>promise2
(1) setTimeout 和 setImmediate
两者很是类似,区别主要在于调用时机不一样。
setTimeout(function timeout () { console.log('timeout'); },0); setImmediate(function immediate () { console.log('immediate'); });
但当两者在异步 i/o callback 内部调用时,老是先执行 setImmediate,再执行 setTimeout
const fs = require('fs') fs.readFile(__filename, () => { setTimeout(() => { console.log('timeout'); }, 0) setImmediate(() => { console.log('immediate') }) }) // immediate // timeout
在上述代码中,setImmediate 永远先执行。由于两个代码写在 IO 回调中,IO 回调是在 poll 阶段执行,当回调执行完毕后队列为空,发现存在 setImmediate 回调,因此就直接跳转到 check 阶段去执行回调了。
(2) process.nextTick
这个函数实际上是独立于 Event Loop 以外的,它有一个本身的队列,当每一个阶段完成后,若是存在 nextTick 队列,就会清空队列中的全部回调函数,而且优先于其余 microtask 执行。
setTimeout(() => { console.log('timer1') Promise.resolve().then(function() { console.log('promise1') }) }, 0) process.nextTick(() => { console.log('nextTick') process.nextTick(() => { console.log('nextTick') process.nextTick(() => { console.log('nextTick') process.nextTick(() => { console.log('nextTick') }) }) }) }) // nextTick=>nextTick=>nextTick=>nextTick=>timer1=>promise1
浏览器环境下,microtask 的任务队列是每一个 macrotask 执行完以后执行。而在 Node.js 中,microtask 会在事件循环的各个阶段之间执行,也就是一个阶段执行完毕,就会去执行 microtask 队列的任务。
接下咱们经过一个例子来讲明二者区别:
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
Node 端的处理过程以下:
浏览器和 Node 环境下,microtask 任务队列的执行时机不一样
Fundebug专一于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java线上应用实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了9亿+错误事件,付费客户有Google、360、金山软件、百姓网等众多品牌企业。欢迎你们免费试用!
转载时请注明做者Fundebug以及本文地址:
https://blog.fundebug.com/2019/01/15/diffrences-of-browser-and-node-in-event-loop/