浏览器:帧&事件循环

测试代码

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性能优化
浏览器:帧&渲染流程

相关推荐
IT瘾君2 分钟前
JavaWeb:Html&Css
前端·html
264玫瑰资源库28 分钟前
问道数码兽 怀旧剧情回合手游源码搭建教程(反查重优化版)
java·开发语言·前端·游戏
喝拿铁写前端39 分钟前
从圣经Babel到现代编译器:没开玩笑,普通程序员也能写出自己的编译器!
前端·架构·前端框架
HED1 小时前
VUE项目发版后用户访问的仍然是旧页面?原因和解决方案都在这啦!
前端·vue.js
拉不动的猪1 小时前
前端自做埋点,我们应该要注意的几个问题
前端·javascript·面试
王景程1 小时前
如何测试短信接口
java·服务器·前端
尤物程序猿1 小时前
【2025面试Java常问八股之redis】zset数据结构的实现,跳表和B+树的对比
数据结构·redis·面试
安冬的码畜日常2 小时前
【AI 加持下的 Python 编程实战 2_10】DIY 拓展:从扫雷小游戏开发再探问题分解与 AI 代码调试能力(中)
开发语言·前端·人工智能·ai·扫雷游戏·ai辅助编程·辅助编程
烛阴2 小时前
Node.js中必备的中间件大全:提升性能、安全与开发效率的秘密武器
javascript·后端·express
小杨升级打怪中2 小时前
前端面经-JS篇(三)--事件、性能优化、防抖与节流
前端·javascript·xss