JS中的事件循环和任务队列( 二 )


JS中的事件循环和任务队列

文章插图
 
从上图中 , 网络请求在执行过程:
1、请求函数被执行 , 传递一个匿名函数作为回调 , 这个函数在将来某个时候 , 当响应(response)可用时执行 。
2、“Script call done!” 被立即输出到控制台 。
3、在将来某时刻 , 响应(response)从服务端返回 , 执行我们的回调函数 , 并将其body输出到控制台 。
调用方与响应的解耦使JavaScript运行时可以在等待异步操作完成并触发其回调前执行其他操作 。在浏览器中 , 用于处理异步事件 , 是由C++来实现的 , 例如DOM事件 , http请求 , setTimeout等(知道了这一点之后 , 在Angular 2中 , 使用了区域 , 这些区域对这些API进行了猴子修补 , 以引起运行时更改检测 , 现在我可以了解它们如何实现此目的 。)在浏览器中 , 当这些API被调用时 , 浏览器将创建进程处理异步的回调函数 。
浏览器Web API-由浏览器创建的线程 , 使用C ++实现 , 用于处理异步事件 , 例如DOM事件 , http请求 , setTimeout等 。
由于这些WebAPI本身不能将执行代码放到堆栈上 , 如果这样做了 , 它将随机出现在代码中间 。由于这些WebAPI本身不能将执行代码放到堆栈上 , 如果这样做了 , 它将随机出现在代码中间 。上面讨论的消息回调队列展现了方法 。任何WebAPI在执行完 , 都会将回调(function)推送到此队列中 。现在 , 事件循环(Event Loop)负责在 队列(Queue) 中执行这些回调 , 并在其为空时将其压入 堆栈(Stack)  。
进入队列 , 并不会立即被执行 , 只有当前Event Loop执行栈中的任务被执行完成后 , 才会被压入执行栈 。
事件循环(Event Loop)的基本工作是同时查看堆栈(Stack)和任务队列(Queue) , 并在将堆栈视为空时将队列中的第一件事推入堆栈 。在处理任何其他消息之前 , 将完全处理每个消息或回调 。
while (queue.waitForMessage()) {queue.processNextMessage();}
JS中的事件循环和任务队列

文章插图
【JS中的事件循环和任务队列】 
在 Web 浏览器中 , 每当发生事件(Event)并附加事件侦听器(Listener)时 , 都将添加消息(Message) 。如果没有侦听器(Listener) , 则事件(Event)将丢失 。因此 , 单击具有 click 事件处理程序的元素(Element)将添加一条消息(Message) - 与任何其他事件一样 。此回调函数(Callback function)的调用将用作调用堆栈中的初始帧 , 由于 JavaScript 是单线程的 , 在堆栈上返回所有调用之前 , 将停止进一步的消息轮询和处理 。后续(同步)函数调用向堆栈添加新的调用帧 。
现在可以看出 , 有很多不同的任务队列 , 由上面可知 , 一般可分为两类 , 1)宏任务 , 2)微任务 。
队列优先级
我先把结论COPY过来 , 有时间再写一篇文章详细说明 。
小结
在JS引擎中 , 我们可以按性质把任务分为两类 , macrotask(宏任务)和 microtask(微任务) 。
浏览器JS引擎中:
macrotask(按优先级顺序排列): script(你的全部JS代码 , “同步代码”), setTimeout, setInterval, setImmediate, I/O,UI rendering
microtask(按优先级顺序排列):process.nextTick,Promises(这里指浏览器原生实现的 Promise), Object.observe, MutationObserver
JS引擎首先从macrotask queue中取出第一个任务 , 执行完毕后 , 将microtask queue中的所有任务取出 , 按顺序全部执行;
然后再从macrotask queue(宏任务队列)中取下一个 , 执行完毕后 , 再次将microtask queue(微任务队列)中的全部取出;
循环往复 , 直到两个queue中的任务都取完 。
所以 , 浏览器环境中 , js执行任务的流程是这样的:
第一个事件循环 , 先执行script中的所有同步代码(即 macrotask 中的第一项任务)
再取出 microtask 中的全部任务执行(先清空process.nextTick队列 , 再清空promise.then队列)


推荐阅读