- node重要性质
事件驱动 非阻塞 I/O 单线程 异步回调
对 node Event Loop(事件循环)的理解特别重要 想必每一位前端对于浏览器的Event Loop已经有所了解
但是在学习node的路上, 理解node的Event Loop也是不可或缺的
- 版本变迁
version < node11
简单概述 Node 11版本之前 任务分配是 先执行全局Script代码,执行完同步代码调用栈清空后,先从微任务队列Next Tick Queue中依次取出所有的任务放入调用栈中执行,再从微任务队列Other Microtask Queue中依次取出所有的任务放入调用栈中执行。然后开始宏任务的6个阶段,每个阶段都将该宏任务队列中的所有任务都取出来执行(注意,这里和浏览器不一样,浏览器只取一个),每个宏任务阶段执行完毕后,开始执行微任务,再开始执行下一阶段宏任务,以此构成事件循环。
version >= node11
Node11之后,也是每个 Macrotask 执行完后,就去执行 Microtask 了,和浏览器的模型一致。
setTimeout(() => {
console.log('t1');
Promise.resolve().then(function() {
console.log('p1');
});
}, 0);
setTimeout(() => {
console.log('t2');
Promise.resolve().then(function() {
console.log('p2');
});
}, 0);
//version < node11: t1 t2 p1 p2
//version >= node11: t1 p1 t2 p2
- node 两个特别的环境函数需要注意 (这里环境函数不是专业名词)
- process.nextTick (微)
- setImmediate (宏)
process.nextTick方法可以在当前"执行栈"的尾部----下一次Event Loop(主线程读取"任务队列")之前----触发回调函数。也就是说,它指定的任务总是发生在所有异步任务之前。
setImmediate方法则是在当前"任务队列"的尾部添加事件,也就是说,它指定的任务总是在下一次Event Loop时执行,这与setTimeout(fn, 0)很像
-
process.nextTick 可以在当前"执行栈"的尾部----下一次Event Loop(主线程读取"任务队列")之前----触发回调函数; 如果在一个执行栈中被注册, process.nextTick总是先于setImmediate和setTimeout执行
-
setImmediate 在当前"任务队列"的尾部添加事件,也就是说,它指定的任务总是在下一次Event Loop时执行 和 setTimeout如果处于同一个执行栈, 不能确定谁先执行(由多种因素决定)
-
有一种情况setImmediate一定比setTimeout先执行: setImmediate和setTimeout同处在一个setImmediate调用栈里面
//示例一
setImmediate(()=> {
console.log(1);
});
setTimeout(()=> {
console.log(2);
}, 0);
//结果可能是 1 2 也可能是 2 1
//示例二
setImmediate(()=> {
setImmediate(()=> {
console.log(1);
setImmediate(()=> {
console.log(2);
});
});
setTimeout(()=> {
console.log(3);
}, 0);
});
//结果: 1 3 2
- 不要觉得奇怪: “上面的代码即使到了编译那一层居然还有有不同的运行结果(不可控)”
- 实际上setImmediate的出现正是为了我们能够在Event Loop再插一手
多个process.nextTick语句总是在当前"执行栈"一次执行完,多个setImmediate可能则需要多次loop才能执行完。事实上,这正是Node.js 10.0版添加setImmediate方法的原因,否则像下面这样的递归调用process.nextTick,将会没完没了,主线程根本不会去读取"事件队列"!
//示例一
process.nextTick(function foo() {
process.nextTick(foo);
});
setTimeout(function callMe() {
console.log('call me ! ! !')
})
//callMe永远不会被调用, 因为 setTimeout(宏)总是需要等待当前执行栈的微任务清空
//示例二
setImmediate(function foo() {
setImmediate(foo);
});
setTimeout(function callMe() {
console.log('call me ! ! !')
})
//callMe将会在第一个或第二个事件队列被调用
node.js
皇权特许 先斩后奏 ~