浏览器的 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 start
promise1
async2 promise
promise2
async1 end
setTimeout
*/
- 打印 ”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 scheduled
someAsyncOperation 读文件后(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");
});
});
/*
nextTick1
setImmediate
nextTick2
setTimeout
*/