面试高频的事件循环

事件循环(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 函数中,再执行之前阻塞的代码

相关推荐
小白小白从不日白4 分钟前
react 组件通讯
前端·react.js
罗_三金14 分钟前
前端框架对比和选择?
javascript·前端框架·vue·react·angular
Redstone Monstrosity21 分钟前
字节二面
前端·面试
东方翱翔28 分钟前
CSS的三种基本选择器
前端·css
Fan_web1 小时前
JavaScript高级——闭包应用-自定义js模块
开发语言·前端·javascript·css·html
yanglamei19621 小时前
基于GIKT深度知识追踪模型的习题推荐系统源代码+数据库+使用说明,后端采用flask,前端采用vue
前端·数据库·flask
千穹凌帝1 小时前
SpinalHDL之结构(二)
开发语言·前端·fpga开发
dot.Net安全矩阵1 小时前
.NET内网实战:通过命令行解密Web.config
前端·学习·安全·web安全·矩阵·.net
叫我:松哥1 小时前
基于Python flask的医院管理学院,医生能够增加/删除/修改/删除病人的数据信息,有可视化分析
javascript·后端·python·mysql·信息可视化·flask·bootstrap
Hellc0071 小时前
MacOS升级ruby版本
前端·macos·ruby