再探Event Loop
之前的认识
Javascript 将所有代码作为单个线程执行(意味着一次只发生一件事,只有一个调用的堆栈);但是,利用某些数据结构,你可以伪装一些多线程的表象(同时发生多项事件)。这种劣根性反而成为它的一种优势。因为它增加了JavaScript编程的简单性。
再一次的认识
1.但是Web上的异步行为如何操作呢?JavaScript不是同步的吗?
此时Event Loop开始登场发挥作用。在大多数浏览器中,每个选项卡都有一个单独的Event Loop,以避免阻塞整个浏览器的繁重处理。
2.microtask微任务是ES6等js语法规定的异步任务
包含:
process.nextTick()
Promise callback
async/await functions
queueMicrotask
3.macrotask/Web APIs 宏任务是由浏览器规定的
包含:
setTimeout(),
setInterval(),
setImmediate(),
Ajax,fetch,
DOM事件,
键盘/鼠标事件
4.微任务执行时机比宏任务要早
5.同步任务早于异步任务执行
6.Event loop中穿插DOM渲染的时机
总结的要点如图所示:
什么是事件循环
大家可以用jsv9000.app这个专门为事件循环而设计的图形化调试网站做演示,以下练习示例使用的此模型。
事件循环如下图所示。它有一个调用堆栈,一个微任务microtask队列,一个宏任务macrotask队列,Web api也是其中一种。这样的循环器被称为事件循环中的一个tick。每个事件只是一个函数回调。
例如,当您的JavaScript应用里发出一个fetch异步请求,从服务器获取一些数据时,您在函数中设置了“响应”代码(“回调函数callback”),并且JS引擎告诉托管环境:“嘿,我现在将暂停执行,但无论何时您完成网络请求,并且您有一些数据,请回调此函数。”
然后,浏览器被设置为监听来自网络的响应,当它有东西要返回给您时,它将通过将回调函数插入到事件循环中来安排执行时间。
这些Web api是什么?本质上,它们是您不能访问的线程,您只能调用它们。它们是浏览器中并发性发挥作用的部分。包含有:
setTimeout()
setInterval()
setImmediate()
Ajax,fetch,DOM事件
键盘/鼠标事件
setTimeout()如何工作setTimeout(() =>{}), 0)以毫秒为单位的回调和计时器作为参数。它设置了一个计时器。当计时器到期时,JavaScript环境将回调到任务队列。
调用堆栈call stack这是执行所有代码的地方。这是一个后进先出队列queue。事件循环持续检查调用堆栈call stack,以查看是否有要运行的函数。
任务队列每当事件循环遇到类似
1 | setTimeout(() =>{}), 0) |
的Web API时,它会从调用堆栈中删除它,然后在计时器之后将回调函数发送到任务队列。一旦调用堆栈为空,任务队列中的函数就会被发送到调用堆栈call stack中执行
微任务队列microtask queue每当事件循环在调用堆栈中遇到一个promise时,它将其发送到微任务队列。一旦调用堆栈为空,微任务队列中的函数就会被发送到调用堆栈call stack执行。微任务队列的优先级高于宏任务队列。因此,promise首先被执行,然后Macrotask Queue中的函数被允许进入调用堆栈。
示例1演示
1 | console.log('Hi'); |
开始执行代码片段,看看会发生什么
1.初始时,调用堆栈call stack、任务队列(task queue=macrotask queue )和微任务队列microtask queue为空。
2.然后console.log(“Hi”)被推入调用堆栈call stack然后执行
3.然后setTimeout(function cb1() { console.log(‘cb1’); }, 5000);被执行并从调用堆栈中移除。浏览器创建一个计时器作为Web api的一部分。它会为你处理倒计时。
4.cconsole.log(‘Bye’)被添加到调用堆栈并执行。
5.在5000毫秒后,计时器完成,它将cb1回调推到任务队列。
6.事件循环检查调用堆栈是否为空。发现事件循环为空,它将cb1回调传递给调用堆栈。
7.执行cb1。
小结
事件循环只是一个与调用堆栈和回调队列保持良好通信的中介。它检查调用堆栈是否空闲,然后通知回调队列。然后回调队列将回调函数传递给Call stack执行。当所有回调函数都被执行时,调用堆栈被释放,全局执行上下文是空闲的。
示例2演示
1 | const one = () => Promise.resolve('one!') |
如上图所示
根据
1.同步任务早于异步任务执行
2.微任务执行时机比宏任务要早
的原则
‘Before Function’所在的console函数作为同步函数开始执行
执行到myFunc的异步函数时,’In function ! ‘所在的console函数被执行
Promise开始进入微任务队列
优先执行’After function ! ‘同步函数
异步promise返回结果’one’