参考:
- JavaScript 运行机制详解:再谈Event Loop
- 深入理解JavaScript的执行过程--单线程的JS
- 细说JavaScript单线程的一些事
- The JavaScript Event Loop: Explained
- 模拟Event Loop执行过程
- Node.js 事件循环一: 浅析
- 理解 Event Loop、Micro Task & Macro Task
- HTML系列:macrotask和microtask
1. js为啥是单线程执行
单线程是指只在一个线程里执行JS代码,但浏览器是多线程的。
1.1 单线程的好处
简单,处理DOM时不会出现并发竞争问题
2. 异步的必要性
让用户体验更流畅
3. 如何实现异步:Event Loop
见参考1,4,5
3.1 调用栈(call Stack)
函数执行上下文。控制执行函数的调用执行。单线程只能有一个call statck, 并且每次只能执行一个任务。栈是有尺寸的,即在一次执行中函数的嵌套调用数量是有限的,如果超过这个限制就会报错。JS执行时遇到这种错误"Uncaught RangeError: Maximum call stack size exceeded"就是说明call statck溢出了。回调函数上限数量取决于statck本身的大小以及statck元素的大小->见参考:
function computeMaxCallStackSize1() {
try {
return 1 + computeMaxCallStackSize1();
} catch (e) {
// Call stack overflow
return 1;
}
}
// 多了形参p,call stack元素就多占了内存
function computeMaxCallStackSize2(p) {
try {
return 1 + computeMaxCallStackSize2();
} catch (e) {
// Call stack overflow
return 1;
}
}
console.log(computeMaxCallStackSize1()); // 两个输出结果不一样
console.log(computeMaxCallStackSize2());
3.2 回调队列(callback queue)
看参考图,生动的动画模拟可以见参考5。当call stack空的时候,主线程查看callback队列里是否有异步任务,如果有则取出执行,执行完后(即call stack变空了)再去查看call back队列是否有异步任务。这个是循环的过程,也叫事件循环(event loop)。
while (queue.waitForMessage()) {
queue.processNextMessage();
}
个人认为事件队列主要实现了异步回调功能。
3.3 非阻塞I/O
首先明确一点,JS本身就没有I/O(网络请求,磁盘读写,用户交互)的,所有的I/O操作都是由宿主执行的,宿主提供相关的API供JS调用。当JS调用IO API时并不会等待宿主的执行结果而是继续执行后面的代码,当IO执行完成后宿主再通知JS,即在eventQueue中插入回调task。
不过还有些I/O是同步的,比如同步XHR请求,alert。尽量避免使用。
3.4 阻塞eventLoop
event loop实现了异步回调,但回调只能一个一个的执行,但前面的一个回调非常耗时时就会阻塞后面的回调执行,用户交互也可能出现卡死。
function sleep(ms) {
var curr = Date.now();
while(Date.now() - curr < ms){};
}
console.log(1)
setTimeout(function(){ // 回调依旧被阻塞
console.log(2)
}, 0)
sleep(2000)
最好开启新线程执行耗时的运算(使用web worker)或者放在服务端运算。
4. Macro-task & micro-task
event loop中除了 callback queue(macro-task)外还有个队列专门处理micro-task。micor-task队列为空时才去处理macro-task队列执行。
当打算以同步的方式处理异步回调(即执行栈为空时立马执行回调函数)时可以采用micro-task方式。
- macrotasks: setTimeout, setInterval, setImmediate, requestAnimationFrame, I/O, UI rendering
- microtasks: process.nextTick, Promises, Object.observe, MutationObserver
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。