浏览器:帧&事件循环

测试代码

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

相关推荐
别拿曾经看以后~36 分钟前
【el-form】记一例好用的el-input输入框回车调接口和el-button按钮防重点击
javascript·vue.js·elementui
我要洋人死39 分钟前
导航栏及下拉菜单的实现
前端·css·css3
川石课堂软件测试42 分钟前
性能测试|docker容器下搭建JMeter+Grafana+Influxdb监控可视化平台
运维·javascript·深度学习·jmeter·docker·容器·grafana
科技探秘人1 小时前
Chrome与火狐哪个浏览器的隐私追踪功能更好
前端·chrome
科技探秘人1 小时前
Chrome与傲游浏览器性能与功能的深度对比
前端·chrome
JerryXZR1 小时前
前端开发中ES6的技术细节二
前端·javascript·es6
七星静香1 小时前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
q2498596931 小时前
前端预览word、excel、ppt
前端·word·excel
小华同学ai1 小时前
wflow-web:开源啦 ,高仿钉钉、飞书、企业微信的审批流程设计器,轻松打造属于你的工作流设计器
前端·钉钉·飞书
problc1 小时前
Flutter中文字体设置指南:打造个性化的应用体验
android·javascript·flutter