面试高频的事件循环

事件循环(Event Loop)

JavaScript是一门单线程的语言,在同一时间只能做一件事,js里面的代码要按照顺序逐行执行代码?比如说我们浏览新闻想要获取新闻图片,如果网络卡顿,获取了很长时间,难道就这样干等着吗?那肯定是不会的,给到用户的体验也是不好的

在js里所有的任务可以被分为同步任务异步任务 ,异步任务里又有微任务宏任务,

当我们打开网站时,网页的渲染过程就是一大堆同步任务,比如页面骨架和页面元素的渲染。而像加载图片音乐之类占用资源大耗时久的任务,就是异步任务

事件循环运行机制

这种事件循环机制是由 JavaScript 的宿主环境来实现的,在浏览器运行环境中由浏览器内核引擎实现,而在 NodeJS 中则由 libuv 引擎实现。

主线程运行时候,产生堆(Heap)和栈(Stack),栈中的代码调用各种外部 API,它们在任务队列中加入各种事件。只要栈中的代码执行完毕,主线程就会通过事件循环机制读取任务队列,依次执行那些事件所对应的回调函数。

运行机制:

  1. 所有同步任务都在主线程上执行,形成一个 执行栈(Execution Context Stack)
  2. 主线程之外,还存在一个 任务队列 (Task Queue)。只要异步任务有了运行结果,就在 任务队列 之中放置一个事件
  3. 一旦 执行栈 中的所有同步任务执行完毕,系统就会读取 任务队列,看看里面有哪些待执行事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行
  4. 主线程不断重复上面的第三步

浏览器环境

JavaScript 的异步任务根据事件分类分为两种:宏任务(MacroTask)和微任务(MicroTask

微任务:一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前

常见的微任务有:

markdown 复制代码
*   Promise.then
*   MutaionObserver
*   Object.observe(已废弃;Proxy 对象替代)
*   process.nextTick(Node.js)

宏任务的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合

常见的宏任务有:

markdown 复制代码
*   setTimeout/setInterval

*   UI rendering/UI事件

*   postMessage、MessageChannel

*   setImmediate、I/O(Node.js)

微任务与宏任务的区别

宏任务与微任务的区别在于队列中事件的执行优先级。进入整体代码(宏任务)后,开始首次事件循环,当执行上下文栈清空后,事件循环机制会优先检测微任务队列中的事件并推至主线程执行,当微任务队列清空后,才会去检测宏任务队列中的事件,再将事件推至主线程中执行,而当执行上下文栈再次清空后,事件循环机制又会检测微任务队列,如此反复循环。

宏任务与微任务的优先级

  • 宏任务的优先级高于微任务
  • 每个宏任务执行完毕后都必须将当前的微任务队列清空
  • 第一个 <script> 标签的代码是第一个宏任务
  • process.nextTick 优先级高于 Promise.then

示例代码

js 复制代码
setTimeout(function() {
    console.log('1');
})

new Promise(function(resolve) {
    console.log('2');
}).then(function() {
    console.log('3');
})

console.log('4'); // 2 4 3 1
  • 这段代码作为宏任务,进入主线程。
  • 先遇到setTimeout,那么将其回调函数注册后分发到宏任务Event Queue。(注册过程与上同,下文不再描述)
  • 接下来遇到了Promisenew Promise立即执行,then函数分发到微任务Event Queue。
  • 遇到console.log(),立即执行。
  • 好啦,整体代码script作为第一个宏任务执行结束,看看有哪些微任务?我们发现了then在微任务Event Queue里面,执行。
  • ok,第一轮事件循环结束了,我们开始第二轮循环,当然要从宏任务Event Queue开始。我们发现了宏任务Event QueuesetTimeout对应的回调函数,立即执行。
  • 结束。

async与awite

async关键字是asynchronous(异步)的简写,用来声明一个函数是异步函数,写在函数的最前面,他会返回一个promise对象

awite可以理解为asynchronous waite(等待异步),他会等待一个异步任务返回的结果

下面这两种方法是等效的

js 复制代码
function fn() {
    return Promise.resolve('TEST');
}

async function asyncFn() {
    return 'TEST';
}

awite

正常情况下,await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值

js 复制代码
async function fn(){
    // 等同于
    // return 123
    return await 123
}
fn().then(a => console.log(a)) // 123

不管await后面跟着的是什么,await都会阻塞后面的代码

js 复制代码
async function fn1 (){
    console.log(1)
    await fn2()
    console.log(2) // 阻塞
}

async function fn2 (){
    console.log('fn2')
}

fn1()
console.log(3)
//1 fn2 3 2 

上面的例子中,await 会阻塞下面的代码(即加入微任务队列),先执行 async 外面的同步代码,同步代码执行完,再回到 async 函数中,再执行之前阻塞的代码

相关推荐
uhakadotcom1 小时前
视频直播与视频点播:基础知识与应用场景
后端·面试·架构
范文杰1 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪1 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪2 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy2 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom3 小时前
快速开始使用 n8n
后端·面试·github
uhakadotcom3 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom3 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom3 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom3 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试