浏览器的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 3 2
执行过程:
- 当前是一个宏任务,依次执行,打印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的六个阶段
- 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
*/
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。