Event Loops标准 | 标准对Event Loops的说明 | |
Node.js 事件循环,定时器和 process.nextTick() | 介绍了nodejs的事件循环和定时器 | |
调用栈 | 介绍js运行调用栈 | |
JS中的栈内存堆内存 |
浏览器端运行JS;V8引擎开源后,JS有机会在服务端运行
浏览器的Event Loop
异步实现:
1.宏观:浏览器多线程
2.微观:Event Loop,事件循环
Task(普通任务,宏任务) :setImmediate(node 的立即执行函数先于setTimeout执行)
microtask(微任务):Object.observe(监听对象变化的方法)、MutationObserver(监听Dom结构变化的API)、postMessage(window对象之间进行通信的方法)
下图紫色部分就是Event Loop (事件循环)
1.首先执行script,script被称为全局任务,也属于task(宏任务);
2.当task执行完以后,执行所有的微任务;
3.微任务全部执行完,再取任务队列中的一个宏任务执行。
一个Event Loop有一个或多个task queue(宏任务队列),每个Event Loop有一个microtask queue(微任务队列)
requestAnimationFrame处于渲染阶段,不属于任务队列
console.log("1");
setTimeout(function() {
console.log("2");
}, 0);
Promise.resolve().then(function() {
console.log("3");
});
console.log("4");
// 1->4->3->2
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
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
Node.js的Event Loop
分3层构成
1.node-core
2.绑定
3.libuv + V8引擎
六个阶段:
- timers:执行timer(定时器)的回调
- pending callbacks:系统操作的回调(待定回调执行延迟到下一个循环迭代的 I/O 回调。)
- idle,pepare:内部使用
- poll:等待新的I/O(输入/输出)事件(轮询:检索新的 I/O 事件;执行与 I/O 相关的回调(几乎所有情况下,除了关闭的回调函数,那些由计时器和setImmediate()调度的之外),其余情况 node 将在适当的时候在此阻塞。)
- check:执行setImmediate回调(检测)
- close callbacks:内部使用【关闭的回调函数:一些关闭的回调函数,如:socket.on('close', ...)】
每个阶段都有一个 FIFO 队列来执行回调。虽然每个阶段都是特殊的,但通常情况下,当事件循环进入给定的阶段时,它将执行特定于该阶段的任何操作,然后执行该阶段队列中的回调,直到队列用尽或最大回调数已执行。当该队列已用尽或达到回调限制,事件循环将移动到下一阶段。
// nodejs模块——fs模块 fs模块用于对系统文件及目录进行读写操作。
const fs = require('fs');// fs.xxx 异步js方法
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`);// 204ms=加载文件4ms + 休息200ms ≠ 100ms,poll会阻塞进程
}, 100);
someAsyncOperation(() => {
const startCallback = Date.now();
while (Date.now() - startCallback < 200) {
// do nothing休息200ms
}
});
如果运行以下不在 I/O 周期(即主模块)内的脚本,则执行两个计时器的顺序是非确定性的,因为它受进程性能的约束:
// timeout_vs_immediate.js
setTimeout(() => {
console.log('timeout');
}, 0);
setImmediate(() => {
console.log('immediate');
});
但是,如果你把这两个函数放入一个 I/O 循环内调用,setImmediate 总是被优先调用
const fs = require('fs');
fs.readFile(__filename, _ => {
setTimeout(_ => {
console.log("setTimeout");
}, 0);
setImmediate(_ => {
console.log("setImmediate");
});
});
// setImmediate->setTimeout
process.nextTick()
是一个异步的node API,但是不属于Event Loop的阶段
调用这个方法会先暂停Event Loop,先执行这个方法回调,之后继续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