JS异步详解 - 浏览器/Node/事件循环/消息队列/宏任务/微任务
栏目: JavaScript · 发布时间: 7年前
内容简介:一个 JavaScript 引擎会常驻于内存中,它等待着我们把JavaScript 代码或者函数传递给它执行在 ES3 和更早的版本中,JavaScript 本身还没有异步执行代码的能力,引擎就把代码直接顺次执行了,异步任务都是宿主环境(浏览器)发起的(setTimeout、AJAX等)。在 ES5 之后,JavaScript 引入了 Promise,这样,不需要浏览器的安排,JavaScript 引擎本身也可以发起任务了
js异步历史
一个 JavaScript 引擎会常驻于内存中,它等待着我们把JavaScript 代码或者函数传递给它执行
在 ES3 和更早的版本中,JavaScript 本身还没有异步执行代码的能力,引擎就把代码直接顺次执行了,异步任务都是宿主环境(浏览器)发起的(setTimeout、AJAX等)。
在 ES5 之后,JavaScript 引入了 Promise,这样,不需要浏览器的安排,JavaScript 引擎本身也可以发起任务了
JS异步实现原理
js为单线程,js引擎中负责解析执行js代码的线程只有一个( 主线程 ),即每次只能做一件事,其他IO操作放入任务队列等待执行,异步过程中, 工作线程 在 异步操作完成后 需要 通知主线程 。那么这个 通知机制 是利用 消息队列 和 事件循环(EventLoop) 实际上, 主线程只会做一件事情 ,就是从消息队列里面取消息、执行消息,再取消息、再执行。当消息队列为空时,就会等待直到消息队列变成非空。而且 主线程只有在将当前的消息执行完成后,才会去取下一个消息
浏览器
概念
- 消息队列 :消息队列是一个 先进先出 的队列,它里面存放着各种消息。
- 事件循环 :事件循环是指 主线程重复从消息队列中取消息、执行的过程。(浏览器至少有一个事件循环,一个事件循环至少有一个任务队列(macrotask))
-
微任务:
- JavaScript 引擎发起的任务 - JS 引擎级别
- promise回调,MutationObserver,process.nextTick,Object.observe
-
宏任务
- 宿主发起的任务,每次的一段js代码执行过程,其实都是一个宏观任务 - 宿主级别
- 整体的js代码,事件回调,XHR回调,定时器(setTimeout/setInterval/setImmediate),IO操作,UI render
- 宏任务和微任务关系:每个macro宏任务会维护一个micro微任务列表
事件循环过程
- 首先我们分析有多少个宏任务;
- 在每个宏任务中,分析有多少个微任务;
- 根据调用次序,确定宏任务中的微任务执行次序;
- 根据宏任务的触发规则和调用次序,确定宏任务的执行次序;
- 确定整个顺序
视图渲染时机:
- 本轮事件循环的microtask队列被执行完之后(不是每轮事件循环都会执行视图更新,浏览器有自己的优化策略)
- 注意:执行任务的耗时会影响视图渲染的时机。通常浏览器以每秒60帧(60fps)的速率刷新页面(16.7ms渲染一帧)所以如果要让用户觉得顺畅,单个macrotask及它相关的所有microtask最好能在16.7ms内完成。
Node
概念
- 非阻塞 I/O 操作:尽管 JavaScript 是单线程处理的——当有可能的时候,它们会把操作转移到系统内核中去,当其中的一个操作完成的时候,内核通知 Node.js 将适合的回调函数添加到 轮询 队列中等待时机执行
事件循环过程
-
过程
- event loop 的每个阶段都有一个任务队列(一个 FIFO 队列来执行回调)
- 当 event loop 到达某个阶段时,将执行该阶段的任务队列,直到队列清空或执行的回调达到系统上限后,才会转入下一个阶段
- 当所有阶段被顺序执行一次后,称 event loop 完成了一个 tick
-
每次事件循环都包含了6个阶段
- timers 阶段 :这个阶段执行timer(
setTimeout、setInterval)的回调 - I/O callbacks 阶段 :执行一些系统调用错误,比如网络通信的错误回调
- idle, prepare 阶段 :仅node内部使用
- poll 阶段 :获取新的I/O事件, 适当的条件下node将阻塞在这里
- check 阶段 :执行
setImmediate()的回调 - close callbacks 阶段 :执行
socket的close事件回调
- timers 阶段 :这个阶段执行timer(
-
timers 阶段
Node 会去检查有无已过期的timer,如果有则把它的回调压入timer的任务队列中等待执行
技术上来说,poll 阶段控制 timers 什么时候执行。
-
poll 阶段
-
poll 阶段主要有2个功能:
- 处理 poll 队列的事件
- 当有已超时的 timer,执行它的回调函数
-
执行过程:当event loop进入 poll 阶段,并且 没有设定的timers(there are no timers scheduled),会发生下面两件事之一:
如果 poll 队列不空,event loop会遍历队列并同步执行回调,直到队列清空或执行的回调数到达系统上限;
如果 poll 队列为空,则发生以下两件事之一:
- 如果代码已经被setImmediate()设定了回调, event loop将结束 poll 阶段进入 check 阶段来执行 check 队列(里的回调)。
- 如果代码没有被setImmediate()设定回调,event loop将阻塞在该阶段等待回调被加入 poll 队列,并立即执行。
- 当event loop进入 poll 阶段,并且 有设定的timers,一旦 poll 队列为空(poll 阶段空闲状态): event loop将检查timers,如果有1个或多个timers的下限时间已经到达,event loop将绕回 timers 阶段,并执行 timer 队列。
- 注意:没有
setImmediate()会导致event loop阻塞在poll阶段,这样之前设置的timer岂不是执行不了了?所以咧,在poll阶段event loop会有一个检查机制,检查timer队列是否为空,如果timer队列非空,event loop就开始下一轮事件循环,即重新进入到timer阶段。
-
process.nextTick() VS setImmediate()
-
process.nextTick()
process.nextTick()
-
setImmediate
对比
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
http://lynnelv.github.io/img/...
http://lynnelv.github.io/img/...
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Impractical Python Projects
Lee Vaughan / No Starch Press / 2018-11 / USD 29.95
Impractical Python Projects picks up where the complete beginner books leave off, expanding on existing concepts and introducing new tools that you’ll use every day. And to keep things interesting, ea......一起来看看 《Impractical Python Projects》 这本书的介绍吧!
html转js在线工具
html转js在线工具
HEX HSV 转换工具
HEX HSV 互换工具