当前位置: 首页 > Web前端 > JavaScript

js事件循环与宏微任务队列-高级前端面试_0

时间:2023-03-27 18:30:02 JavaScript

背景一个愉快的下午。有朋友给我分享了一个头条面试题,如下:log('async2')}console.log('scriptstart')setTimeout(function(){console.log('setTimeout')},0)async1();newPromise(function(resolve){console.log('promise1')resolve();}).then(function(){console.log('promise2')})console.log('scriptend')本题主要考察同步任务和异步任务:setTimeout,promise,对async/await执行顺序的理解程度。(建议大家先自己做o)当时因为对async和await了解不多,所以答的都是错的:(),所以没记录下来,然后就去看了文章整理一下我的思路。现在写在下面以供将来参考。js事件轮询的一些概念在这里,首先需要了解几个概念:同步任务、异步任务、任务队列、microtask、macrotask同步任务是指在主线程上排队等待执行的任务。执行后面的任务;异步任务是指不进入主线程,而是进入“任务队列”的任务。等待同步任务执行完毕后,轮询执行异步任务队列中的任务macrotask,即宏任务。宏任务队列就相当于我们常说的任务队列。宏任务是由宿主环境分发的异步任务。当轮询事件时,总是检查任务队列并一项一项执行。“任务队列”是一个先进先出的数据结构,前面的事件先被主线程读取。Microtask即microtask,js引擎分发的任务,总是被添加到当前任务队列的尾部执行。另外,在处理microtasks的过程中,如果有新加入的microtasks,也会被加入到队列尾部执行。注意与setTimeout(fn,0)的区别:setTimeOut(fn(),0)指定任务应该在主线程最早可用的空闲时间执行,即尽可能早。它将一个事件添加到“任务队列”的末尾,因此它不会被执行,直到同步任务和“任务队列”的现有事件都被处理完。总结一下:taskqueue,microtask,macrotask一个事件循环有一个或多个任务队列。(任务队列就是宏任务队列)每个事件循环都有一个微任务队列,或微任务队列,当任务被推入队列(微/宏)时,我们的意思是准备工作已完成,因此现在可以执行任务。所以我们可以得到js的执行顺序:开始->取第一个任务队列任务执行(可以认为同步任务队列就是第一个任务队列)->取所有microtask任务依次执行->取第一个任务队列下一个任务队列中的任务并执行它们->取出所有微任务任务并重新执行它们->...这个循环是常见的一些宏任务和微任务:和Await都是异步解决方案。Promise是一个构造函数,在调用时会生成一个Promise实例。then函数中定义的回调函数将在Promise的状态发生变化时被调用。我们都知道这个回调函数不会立即执行。它是一个微任务,会被添加到当前任务队列的尾部,并在下一轮任务开始执行之前执行。async/await成对出现,async标记的函数会返回一个Promise对象,可以使用then方法添加回调函数。await之后的语句会被同步执行。但是await下面的语句会作为microtask加入到当前任务队列的尾部,异步执行。不记得答案的题目一起来看看吧!继续往下看,暖暖准备的话题,不用翻了:)asyncfunctionasync1(){console.log('async1start')awaitasync2()console.log('async1end')}asyncfunctionasync2(){console.log('async2')}console.log('scriptstart')setTimeout(function(){console.log('setTimeout')},0)async1();newPromise(function(resolve){console.log('promise1')resolve();}).then(function(){console.log('promise2')})console.log('scriptend')=node10version是这个结果:scriptstart->async1start->async2->promise1->scriptend->promise2->async1end->setTimeoutasync1开始->async2->promise1->脚本结束->async1结束->promise2->setTimeout按照上面写的js执行顺序可以得到正确的结果,但是最后有两个答案,为什么会有两个结果呢?我们可以看到两个结果中async1end和Promise2的顺序是有区别的。我猜是因为不同版本的node执行await的方式不同,导致下面的代码await进入任务队列的时机不同。详见如何在V8中优化JavaScript异步编程?《深入了解await》里面的简单理解就是asyncfunctionf(){awaitpconsole.log(1);}//node.js8和要推广的标准应该解析成下面的functionf(){Promise.resolve(p).then(()=>{console.log(1)})}//剩下的版本要解析成下面的函数f(){newPromise(resolve=>{resolve(p)}).then(()=>{console.log(1)})}以上两个区别主要是:当Promise.resolve的参数为promise对象时,直接返回Promise对象,then函数更改Promise对象立即执行。老版本在解析await的时候会重新生成一个Promise对象。虽然promise肯定会resolve到p,但是这个过程本身是异步的,即新promise的resolve过程现在进入队列,所以不会立即调用promise的then,而是等到当前队列执行完前面提到的resolve过程会被调用,然后then函数就会被执行。(下面的实践例子会解释当参数是promise时resolve()是如何执行的。)不用担心没有解决的问题,只有一个道理。根据TC39最近的决议,await将直接使用与Promise.resolve()相同的语义。最后,我们使用最新的解析来分析本题(在Chrome环境下)可能的执行过程:定义函数async1和async2。输出'scriptstart'将setTimeout中的回调函数(macrotask)添加到下一轮任务队列中。因为这段代码之前没有进行任何异步操作,等待时间为0s。所以回调函数会立即放在下一轮任务队列的开头。执行异步1。我们知道async函数中await标签之前的语句和await之后的语句是同步执行的。因此,这里依次输出了“async1start”和“async2start”。此时暂停执行后面的语句,将后面的语句放在当前队列的尾部。继续同步任务。输出“Promise1”。然后将函数放在当前队列的末尾。然后输出'scriptend',注意此时只是同步任务执行完毕,当前任务队列中的任务还没有执行完,新增了两个微任务!队列是先进先出的结构,所以这里先输出'async1end',再输出'Promise2',那么第一轮任务队列就真正结束了。然后执行下一个任务列表中的任务。执行setTimeout里面的异步函数。输出“设置超时”。在stackoverflow上练习一个问题resolvedPromiseThen=Promise.resolve().then(res=>{console.log('promise1')})resolvedPromiseThen.then(()=>{console.log('promise2')}).then(()=>{console.log('promise3')})Result:promise1->promise2->resolvePromiseresolved->promise3这个问题真的很迷惑。为什么第三行显示'resolvePromiseresolved'?和舍友商量了一夜无果。其实这个题目的难点在于如何解析一个Promise对象,js引擎会如何处理它。我们知道,当Promise.resolve()的参数是Promise对象时,会直接返回Promise对象。但是当resolve()的参数是Promise对象时,情况就会不同:resolve(resolvedPromise)//等同于:Promise.resolve().then(()=>resolvedPromise.then(resolve));所以这里Onceexecutedhere:()=>resolvedPromise.then(resolve,reject)在第一个then函数中是一个微任务。将放在当前任务列表的末尾,然后Promise1将放在任务列表的末尾。没有开始执行任务列表的同步操作。此时因为resolvedPromise是一个resolvedPromise,直接执行then函数,将resole()函数放在当前队列尾部的then函数中,然后输出Promise1。将Promise2放在队列的末尾。当resole()被执行时,resolvePromise最终成为一个resolvedPromise对象,并将'resolvePromiseresolved'放在当前任务列表的末尾。输出承诺2。将Promise3放在当前任务队列的末尾。输出resolvePromise已解决。最后输出Promise3。结尾!这里的几段代码比较重要,解释了js是如何执行这些新特性的。最后,如果我说错了,请指正。