完整高频题库仓库地址:https://github.com/hzfe/awesome-interview完整高频题库阅读地址:https://febook.hzfe.org/相关题什么是浏览器事件循环为什么浏览器需要事件循环?Node.js中的事件循环回答了关键点。任务队列。异步、非阻塞浏览器需要事件循环来协调事件、用户操作、脚本执行、渲染、网络请求等。通过事件循环,浏览器可以使用任务队列来管理任务,让异步事件非阻塞地执行。每个客户端对应的事件循环是相对独立的。深入知识点1.什么是浏览器事件循环在计算机中,事件循环是等待和发送消息和事件的程序结构。——WikipediaEventLoop可以理解为消息分发器,通过接收和分发不同类型的消息,使得执行程序的事件调度更加合理。浏览器事件循环是以浏览器为宿主环境实现的事件调度,操作顺序如下:执行同步代码。执行一个宏任务(如果不在执行栈中则从任务队列中获取)。如果在执行过程中遇到微任务,则将其添加到微任务的任务队列中。宏任务执行完毕后,立即(按顺序)执行当前微任务队列中的所有微任务。当前宏任务执行完毕后,检查渲染,然后渲染线程接管渲染。渲染完成后,JavaScript线程继续接管,开始下一个循环。下图展示了这个过程:图片来源JSCONFEU20142.为什么浏览器需要事件循环由于JavaScript是单线程的,JavaScript主线程和渲染线程是互斥的,如果异步操作(比如上面提到的WebAPIs)阻止JavaScript执行将导致浏览器冻结。事件循环为浏览器引入了一个任务队列(taskqueue),从而可以非阻塞的方式执行异步任务。浏览器事件循环在处理异步任务时,并不等待其返回结果,而是挂起该事件,继续执行栈中的其他任务。当异步事件返回结果时,将其放入任务队列。回调放入任务队列后不会立即执行,而是等待当前执行栈中的所有任务执行完毕。主线程处于空闲状态,主线程会搜索任务。队列中是否有任务,如果有,取出第一个事件,将这个事件对应的回调放入执行栈,执行里面的同步代码。3、宏任务和微任务异步任务分为两大类:宏任务(macrotask)和微任务(microtask),两者的执行优先级也不同。宏任务主要包括:脚本(整体代码)、setTimeout、setInterval、setImmediate、I/O、UI交互事件。Microtasks主要包括:Promise、MutationObserver等。当当前执行栈为空时,主线程会检查microtask队列中是否有事件。如果不存在,则去宏任务队列中取出一个事件,将相应的回调加入当前执行栈;如果存在,则依次执行队列中事件对应的回调,直到微任务队列为空,然后去宏任务队列中取出最前面的事件,将对应的回调加入当前执行堆。如此反复,进入循环。我们通过一个具体的例子来分析一下:Promise.resolve().then(()=>{//microtask1console.log("Promise1");setTimeout(()=>{//macrotask2console.log("setTimeout2");},0);});setTimeout(()=>{//宏任务1console.log("setTimeout1");Promise.resolve().then(()=>{//微任务2console.log("Promise2");});},0);最终的输出顺序是:Promise1=>setTimeout1=>Promise2=>setTimeout2。具体过程如下:执行同步任务。Microtask1进入microtask队列,macrotask1进入macrotask队列。查看microtask队列,执行microtask1,打印Promise1,生成macrotask2,进入macrotask队列。查看宏任务队列,执行宏任务1,打印setTimeout1,生成微任务2,进入微任务队列。查看microtask队列,执行microtask2,打印Promise2。检查宏任务队列,执行宏任务2,打印setTimeout2。4.Node.js中的事件循环在Node.js中,事件循环的状态与浏览器中的大致相同。不同之处在于Node.js有自己的一套模型。Node.js中事件循环的实现依赖于libuv引擎。下图简单介绍了事件循环的运行顺序:图片来源Node.js官网定时器:该阶段执行已经setTimeout()和setInterval()的调度回调函数。挂起的回调:I/O回调,其执行被推迟到下一个循环迭代。空闲,准备:仅供系统内部使用。poll:检索新的I/O事件;执行I/O相关的回调(几乎在所有情况下,除了关闭的回调函数,那些由定时器和setImmediate()调度的),其余的节点都会在适当的时候阻塞在这里。check:setImmediate()回调函数在这里执行。closecallbacks:一些关闭的回调函数,如:socket.on('close',...)。在事件循环的每次运行之间,Node.js检查它是否正在等待任何异步I/O或计时器,如果没有,则完全关闭。需要注意的是,宏任务和微任务的执行顺序在不同版本的Node.js中表现不同。同样通过一个具体的例子来分析:setTimeout(()=>{console.log("timer1");Promise.resolve().then(function(){console.log("promise1");});},0);setTimeout(()=>{console.log("timer2");Promise.resolve().then(function(){console.log("promise2");});},0);在Node.jsv11及以上版本中,一旦一个stage中的macrotask(setTimeout、setInterval和setImmediate)执行完毕,microtask队列就会立即执行,所以输出顺序为timer1=>promise1=>timer2=>promise2。在Node.jsv10及以下版本中,这取决于第一个计时器执行完时第二个计时器是否在完成队列中。如果第二个计时器不在完成队列中,则输出顺序为timer1=>promise1=>timer2=>promise2。如果第二个定时器已经在完成队列中,则输出顺序为timer1=>timer2=>promise1=>promise2。参考swhatwgeventloopswikipediaeventloopsNode.jseventloops
