浏览器的Event Loop
异步实现
- 宏观:浏览器多线程
- 宏观:Event Loop事件循环,实现异步的一种机制
材料:
Event Loops规范
音讯队列和事件循环(写得很清晰)
宏工作和微工作
宏工作(macro task)
- JavaScript 脚本执行事件
- setTimeout/setInterval 定时器
- setImmediate
- I/O操作
- UI rendering
微工作(micro task)
- Promise
- Object.observe
- MutationObserver
- postMessage
材料:宏工作和微工作(剖析的很具体)
Event Loop运行过程
紫色局部即Event Loop的过程
JS中的栈内存堆内存
栈是一个先进后出的数据结构
调用栈
- 每调用一个函数,解释器就会把该函数增加进调用栈并开始执行。
- 正在调用栈中执行的函数还调用了其它函数,那么新函数也将会被增加进调用栈,一旦这个函数被调用,便会立刻执行。
- 以后函数执行结束后,解释器将其清出调用栈,继续执行以后执行环境下的残余的代码。
函数调用时会调用一些异步函数(定时器,Promise,Ajax等),相应的异步解决模块对应的线程就会往工作队列里增加事件。
从工作队列中取出一个宏工作,该宏工作执行完,调用栈为空时,会执行所有的微工作(一个须要异步执行的函数,执行机会是在主函数执行完结之后、以后宏工作完结之前)。
持续事件循环,再取工作队列中的一个宏工作执行。
Event Loop解决模型
代码剖析1:
console.log("1");setTimeout(function() { console.log("2");}, 0);Promise.resolve().then(function() { console.log("3");});console.log("4");// 输入:1 4 2 3
执行过程:
- 以后是一个宏工作,顺次执行,打印1 4后,宏工作队列空
- 查看微工作队列,执行Promise.resolve().then(),打印3,微工作队列空
- 从新渲染,工作队列里有定时器工作,执行,打印2
一个Event Loop有一个或多个工作队列,每个Event Loop有一个微工作队列。
requestAnimatinFrame不在工作队列,处于渲染阶段。(待学习)
代码示例2:
new Promise()传入的函数参数,在进行new Promise()时会同步执行
console.log("start");setTimeout(() => { console.log("setTimeout"); new Promise(resolve => { console.log("promise inner1"); resolve(); }).then(() => { console.log("promise then1"); });}, 0);new Promise(resolve => { console.log("promise inner2"); resolve();}).then(() => { console.log("promise then2");});// start// promise inner2 这一步是同步执行的// promise then2 这里是异步,微工作// setTimeout// promise inner1// promise then1
- 打印start
- new Promise()传入的函数参数,在进行new Promise()操作时会同步执行,打印promise inner2
- 执行微工作,打印promise then2
- 事件循环,执行宏工作setTimeout,打印setTimeout
- 执行new Promise(),打印promise inner1
- 执行微工作,打印promise then1
代码示例3:
async function async1() { console.log("async1 start"); await async2(); console.log("async1 end");}async function async2() { return Promise.resolve().then(_ => { console.log("async2 promise"); });}console.log("start");setTimeout(function() { console.log("setTimeout");}, 0);async1();new Promise(function(resolve) { console.log("promise1"); resolve();}).then(function() { console.log("promise2");});/*start async1 startpromise1async2 promisepromise2async1 endsetTimeout*/
- 打印"start"
- 执行async函数async1,打印"async1 start"
- 执行await async2(); async2也是一个async函数,返回一个Promise对象,这里是一个微工作,放入微工作队列。await是期待前面函数的执行后果,因而暂停在这里。
- 执行new Promise(),打印"promise1",这里也有一个微工作,也放进微工作队列。
- 宏工作执行完,当初查看微工作队列,按程序执行,先打印"async2 promise",再打印"promise2"。当前任务完结。
- 进入下一个事件循环,setTimeout的工夫设置为0,必定已达到,setTimeout已在工作队列中,执行,打印"setTimeout"。
Node.js的Event Loop
Node.js架构图
- node-core API外围JS库
- 绑定 负责包装和裸露libuv和JS的其余低级性能
- V8引擎 JS引擎,是JS能够运行在服务端的根底
- libuv Node底层的I/O引擎,负责Node API的执行,将不同任务分配给不同的线程,以异步的形式将工作的执行后果返回给V8引擎,是Node异步编程的根底。
Node.js的Event Loop的六个阶段
Node.js官网文档
- timers(定时器) 执行定时器的回调
- pending callbacks(待定回调) 零碎操作的回调
- idle,prepare 外部应用
- poll(轮询) 期待新I/O事件
- check(检测) 执行setImmeidate回调
- close callbacks(敞开的回调函数) 外部应用
次要须要关注1、4、5阶段
每一个阶段都有一个callbacks的先进先出的队列须要执行。当event loop运行到一个指定阶段时,该阶段的FIFO队列将会被执行,当队列的callback执行完或者执行的callbacks数量超过该阶段的下限时,event loop会转入下一阶段。
poll阶段
次要性能:
- 计算应该被block多久(须要期待I/O操作)
- 解决poll队列的事件
代码剖析1:
const fs = require('fs');function someAsyncOperation(callback) { fs.readFile(__dirname, callback); // 异步读文件}const timeoutScheduled = Date.now(); // 以后工夫setTimeout(() => { const delay = Date.now() - timeoutScheduled; console.log(`${delay}ms have passed since I was scheduled`); // 多少ms后执行}, 100);someAsyncOperation(() => { const startCallback = Date.now(); // 延时200ms while (Date.now() - startCallback < 200) { // do nothing }});/* 输入:204ms have passed since I was scheduledsomeAsyncOperation读文件后(4ms),进入poll阶段执行回调,睡眠200ms,poll空,查看是否有到工夫的定时器,执行setTimeout*/
代码剖析2:
const fs = require('fs');fs.readFile(__filename, () => { setTimeout(() => { console.log("setTimeout"); }, 0); setImmediate(() => { console.log("setImmediate"); });});/* 输入:setImmediate setTimeout读文件后,执行回调有setImmediate的回调进入check阶段没有才会期待回调退出poll队列所以先输入setImmediate*/
process.nextTick()
是一个异步的node API,但不属于event loop的阶段
调用nextTick时,会将event loop停下来,执行完nextTick的callback后再继续执行event loop
const fs = require("fs");fs.readFile(__filename, () => { setTimeout(() => { console.log("setTimeout"); }, 0); setImmediate(() => { console.log("setImmediate"); process.nextTick(() => { console.log("nextTick2"); }); }); process.nextTick(() => { console.log("nextTick1"); });});/*nextTick1setImmediatenextTick2setTimeout*/