ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

面试热点: JavaScript 事件循环:Event Loop、宏任务、微任务与执行顺序

面试热点: JavaScript 事件循环:Event Loop、宏任务、微任务与执行顺序 JavaScript Event Loop宏任务、微任务与 async/awaitJavaScript 主线程是单线程执行的也就是说同一时间只能执行一段 JS 代码。但浏览器中又存在很多异步操作例如setTimeout 网络请求 点击事件 Promise如果 JavaScript 每遇到一个异步任务都停下来等待页面就会卡住。因此浏览器会把部分异步任务交给运行环境处理等任务完成后再安排对应的代码重新执行。负责协调这些任务执行顺序的机制就是Event Loop事件循环。一、JavaScript 运行时有哪些部分可以先简单理解成JavaScript 引擎 浏览器 / Node.js 等宿主环境JavaScript 引擎主要负责执行代码例如变量计算 函数调用 Promise async / await浏览器或 Node.js 则提供很多额外能力例如setTimeout setInterval DOM 事件 网络请求 I/O运行过程中主要涉及Call Stack 调用栈 Task Queue 任务队列 Microtask Queue 微任务队列 Event Loop 事件循环普通同步代码会直接进入调用栈执行。二、异步任务怎么执行例如setTimeout(() { console.log(timer) }, 1000) console.log(hello)执行过程可以理解为执行 setTimeout ↓ 浏览器负责计时 ↓ JavaScript 继续执行 ↓ 输出 hello ↓ 计时结束 ↓ setTimeout 回调进入任务队列 ↓ Event Loop 安排回调执行所以最终结果是hello timer三、什么是宏任务常见的宏任务有script 整体代码 setTimeout setInterval I/O 部分事件回调例如setTimeout(() { console.log(宏任务) })setTimeout本身是浏览器或 Node.js 提供的能力不是 JavaScript 语言本身负责计时。可以简单理解为setTimeout ↓ 浏览器 / Node.js 处理 ↓ 回调进入任务队列 ↓ 宏任务所以宏任务通常和定时器、事件、I/O 等宿主环境提供的能力有关。四、什么是微任务常见微任务有Promise.then Promise.catch Promise.finally queueMicrotask MutationObserver async / await 后续代码例如Promise.resolve().then(() { console.log(微任务) }).then()中的回调会进入微任务队列Promise 属于 JavaScript 的 ECMAScript 规范因此很多微任务都和Promise 这套异步机制有关。五、Promise 本身就是微任务吗不是。例如new Promise((resolve) { console.log(1) resolve() }).then(() { console.log(2) })这里console.log(1)是同步执行的。真正属于微任务的是.then(() { console.log(2) })所以要记住Promise 构造函数立即执行then / catch / finally 的回调属于微任务。六、宏任务和微任务有什么区别两者主要有三个区别。1. 来源不同可以粗略理解为宏任务 → 更多来自浏览器 / Node.js → setTimeout、I/O、事件等 微任务 → 很多来自 Promise 机制 → Promise.then、async/await 等但不能绝对理解成宏任务 浏览器 微任务 JS 引擎实际运行时两边会互相配合。2. 队列不同宏任务进入Task Queue 任务队列微任务进入Microtask Queue 微任务队列3. 执行时机不同最重要的规则是当前任务执行完后会先清空所有微任务再执行下一个宏任务。因此执行顺序可以记成当前宏任务 ↓ 执行同步代码 ↓ 清空所有微任务 ↓ 浏览器可能渲染 ↓ 下一个宏任务 ↓ 再次清空微任务最简单的口诀同步代码 → 微任务 → 下一个宏任务七、经典例子console.log(1) setTimeout(() { console.log(2) }, 0) Promise.resolve().then(() { console.log(3) }) console.log(4)最终结果1 4 3 2因为当前这整段script本身可以看作一个宏任务。执行流程是当前宏任务 script ↓ 先执行同步代码 1 4 ↓ 清空微任务 3 ↓ 执行下一个宏任务 setTimeout 2这里要注意不能简单说“微任务一定比宏任务先”。因为当前整段script本身就可以看作一个任务。更准确地说当前任务先执行当前任务结束后微任务优先于下一个宏任务。八、setTimeout(fn, 0) 为什么不是立即执行例如setTimeout(() { console.log(A) }, 0) console.log(B)结果B A因为setTimeout(fn, 0)并不表示立即执行而是表示当时间条件满足后把回调安排到后续任务中等待执行。它仍然要等待当前同步代码执行完成 ↓ 微任务执行完成 ↓ 才有机会执行九、什么是 asyncasync用来声明异步函数。例如async function test() { return 100 }async函数调用后一定返回Promise所以上面的代码可以大致理解成function test() { return Promise.resolve(100) }所以要记住async 函数一定返回 Promise。十、什么是 awaitawait通常写在async函数中async function test() { const result await Promise.resolve(100) console.log(result) }await的作用可以理解为暂停当前 async 函数后续代码等待结果完成后再继续执行。但是await 不会阻塞整个 JavaScript 主线程。例如async function test() { console.log(1) await Promise.resolve() console.log(2) } console.log(3) test() console.log(4)结果3 1 4 2执行过程console.log(3) → 同步执行 test() → 执行 console.log(1) 遇到 await → 当前 async 函数暂停 console.log(4) → 继续同步执行 同步代码结束 → 执行 await 后续代码 console.log(2)所以可以简单记await 前面的代码 → 同步执行 await 后面的代码 → 后续以微任务方式继续执行十一、await 后面不是 Promise 呢例如async function test() { console.log(1) await 100 console.log(2) } test() console.log(3)结果1 3 2虽然100不是 Promise但可以大致理解成await Promise.resolve(100)所以await后面的代码仍然会延后执行。十二、宏任务里面创建微任务例如setTimeout(() { console.log(1) Promise.resolve().then(() { console.log(2) }) console.log(3) }) setTimeout(() { console.log(4) })结果1 3 2 4第一个宏任务执行1 3同时产生微任务2当前宏任务结束后必须先清空微任务2然后才执行下一个宏任务4所以最终1 3 2 4十三、做 Event Loop 题怎么判断以后看到执行顺序题可以按照这个顺序更准确1. 开始执行当前宏任务例如整个 script 2. 执行其中的同步代码 3. 遇到 Promise.then → 放入微任务队列 4. 遇到 setTimeout → 放入后续宏任务队列 5. 当前宏任务执行结束 6. 清空所有微任务 7. 执行下一个宏任务 8. 再次清空微任务 9. 继续循环遇到await时再记住await 前面 → 同步执行 await 后面 → 微任务阶段继续执行总结Event Loop 的作用就是让单线程 JavaScript 能够协调同步代码和异步任务。宏任务通常包括setTimeout setInterval I/O 事件回调微任务通常包括Promise.then Promise.catch Promise.finally queueMicrotask async / await 后续代码两者最重要的区别是来源不同 队列不同 执行时机不同整个 Event Loop 最核心的执行顺序当前任务 ↓ 同步代码 ↓ 清空所有微任务 ↓ 下一个宏任务 ↓ 清空所有微任务 ↓ 继续循环最后记住一句话当前任务先执行当前任务结束后先清空所有微任务再执行下一个宏任务。
返回列表