浏览器:帧&事件循环

测试代码

javascript 复制代码
 function btn() {
        console.log('test btn');
        // btn(); 第一种情况
        // setTimeout(btn,0) 第二种情况
        // Promise.resolve().then(btn) 第三种情况
       
    }
    const normal = document.getElementById("normal")
    normal.addEventListener('click', btn)

先有问题再有答案

  1. 上面的三种死循环 UI表现如何 会卡死嘛
  2. 对js堆栈有哪些影响 无限递归 会报错嘛?
  3. 浏览器的一帧如何处理js任务
  4. 一帧结合事件循环 会有哪些情况
  5. 异步任务会导致栈溢出嘛?
  6. 异步任务会导致UI卡死嘛

一帧

浏览器的一帧 关键节点只有两个 主要是执行js和渲染流程,这两个任务。其他浏览器相关api的调用 这里先不讨论。具体可以参考文末的相关文章。

从图中可以看出js中宏任务和微任务会执行几轮 这里具体的执行次数 浏览器会结合当前的运行状态来动态判断。

通常情况下,在一帧的时间内,浏览器会尽可能地执行JavaScript代码,浏览器会在一帧的结束时进行一次页面渲染。因为页面渲染(包括回流&重绘)是一个相对昂贵的操作,频繁地进行页面渲染会消耗大量的CPU和GPU资源,降低页面的性能。

我们需要注意的是执行完宏任务后一定会执行微任务 并且清空当前的微任务队列。然后再来判断是继续执行js还是开启渲染流程。

event-loop

同步stack:

scss 复制代码
 function btn() {
    btn();
 }

结果: UI卡死 抛出栈溢出异常

此时一帧如图 在浏览器抛出栈溢出错误之前,页面会卡死,因为js执行 渲染被阻塞了。

event-loop如图 直接调用btn函数会导致同步的无限递归,这个操作会迅速耗尽调用栈空间,在很短的时间内引发"RangeError: Maximum call stack size exceeded"错误。

异步macrosTask setTimeout:

scss 复制代码
 function btn() {
    setTimeout(btn,0)
 }

结果:UI可正常滚动 不会抛出栈溢出异常

此时一帧如图 setTimeout 引入了延迟,将下一个btn调用放到事件循环队列中,而不是直接递归调用。这允许浏览器在两次调用之间处理其他事件,包括UI事件,比如滚动和渲染。因此,即使btn函数持续调用,页面也不会立即卡死。

event-loop如图 由于setTimeout的异步性质,它不会导致调用栈溢出。每次setTimeout都会在新的调用栈上运行btn,因此不会出现调用栈大小的异常。

异步Microtask promise:

scss 复制代码
 function btn() {
    Promise.resolve().then(btn);
 }

结果: UI卡死 不会抛出栈溢出异常

此时一帧如图

脚本通过Promise不断地以递归方式创建微任务,无限递归的生成微任务 微任务队列永远不会为空 js线程会一直执行微任务 浏览器没有机会去执行其他宏任务,如UI渲染或事件监听器的调用, 这会导致UI渲染被阻塞,界面无法响应用户操作,体验到的结果就如同UI卡死一样。

event-loop如图

和setTimeout一样,由于Promise的异步特性和微任务的特点,不会导致调用栈溢出。每次Promise解决时,都会开启一个新的任务来运行btn,不会累积调用栈。

总结

js长时间执行会导致页面一帧中的渲染被推迟 页面会卡顿或者卡死...

栈溢出是指函数调用时,因为调用栈(也就是保存函数调用信息的栈结构)所能容纳的激活记录(activation record,每次函数调用的相关信息)数量超出了限制。这种情况通常发生在深度递归或无限递归的同步函数调用中,因为每次函数调用前没有完成上一次调用,导致调用栈越积越多,最终超出栈的容量限制。

对于异步的宏任务和微任务来说,它们不会直接导致栈溢出,原因如下:

异步宏任务 :JavaScript引擎使用事件循环来管理异步操作,宏任务(js三座大山之异步七实现宏任务的N种方式)在每次事件循环迭代中执行,宏任务之间会有断点,每个宏任务完成后,调用栈都会清空,然后处理下一个宏任务。这意味着即使是连续安排的多个宏任务,也不会导致调用栈累积而溢出。

异步微任务 :微任务(js三座大山之异步六实现微任务的N种方式)在当前宏任务完成后、下一个宏任务开始前执行。虽然微任务是连续执行的,但每个微任务都是独立入栈的;即使它们形成了长队列,每次只处理一个微任务,处理完后就从调用栈中弹出。因此,即使微任务队列很长,每次执行完毕都会清空调用栈,不会累积导致栈溢出

相关文章

js三座大山之异步一单线程,event loop,宏任务&微任务
js三座大山之异步五基于异步的js性能优化
浏览器:帧&渲染流程

相关推荐
腾讯TNTWeb前端团队5 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
uhakadotcom8 小时前
视频直播与视频点播:基础知识与应用场景
后端·面试·架构
范文杰8 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪9 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪9 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy9 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom10 小时前
快速开始使用 n8n
后端·面试·github
uhakadotcom10 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom10 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom10 小时前
React与Next.js:基础知识及应用场景
前端·面试·github