前言 趁空闲时间又开了个新坑,记录下目前自己遇到过的一些前端性能优化相关经验总结 希望有朝一日我能把所有还在 draft 阶段的坑都填了 这次准备发一系列文档,预计在6月前完工。 因写内存泄漏文档时发现坑挖太大了不好填,于是这次改进为挖多个小坑一个个填 内存泄漏文档的坑也准备拆一下,不然填不动了,希望有朝一日能完工 何时应该考虑优化性能 性能优化的目标 先考虑这样的例子:玩游戏的时候掉帧,会让玩家很难受,而当开启显卡的帧生成功能时,尽管它实际上增加了输入和画面延迟,但玩家几乎感知不到,反而会感觉游戏的体验更流畅了。而帧生成做的事情,其实是将帧数提升到玩家的舒适范围内,并且保证玩家尽可能感知不到额外的输入延迟,对玩家而言就是游戏性能变得更优了。 类似地,当我们自己使用软件时,一般只有在出现明显卡顿或者延迟的时候才会感知到。 故性能优化的目标可以概括为:将延迟和卡顿降低到用户的可接受范围内。 优化的时机 先上结论:绝大部分情况下,只有出现性能瓶颈的时候才应该考虑优化性能。 性能优化的投入与回报类似一个对数曲线,越往后,付出的成本和回报就越不成正比。因此,如何平衡软件性能与开发成本通常是我们每个开发者都要面临的问题。当然,如果你是个人开发者 or 技术爱好者,那么保证自己的产品在每个细节上都做到性能优秀可能更加适合,但如果是一个全职开发者,并且公司是业务驱动而非技术驱动,那么保证自己可以快速实现功能可能是更重要的。 大部分情况下公司都是业务驱动,即使有些公司声称自己是技术驱动的,个人认为真正的技术驱动公司只会在老板不在乎利润的情况下出现,almost impossible 🙁 因此,在出现性能瓶颈,也就是用户能感知到卡顿或者延迟的时候再去考虑性能优化,是实际开发工作中的性价比之选。 常见前端性能指标 指标概览 让我们先来看一下目前在前端领域都有哪些常见的指标(留下印象即可): First Contentful Paint (FCP):首次内容绘制,衡量从网页开始加载到网页任何部分呈现在屏幕上所用的时间 Largest Contentful Paint (LCP):最大内容绘制,衡量从网页开始加载到屏幕上渲染最大的文本块或图片/视频元素所用的时间 First Input Delay (FID):首次输入延迟,衡量从用户首次与页面互动(点击链接、点按按钮或使用由 JavaScript 提供支持的自定义控件)到浏览器实际能够响应该互动的时间 Interaction to Next Paint (INP):衡量与网页进行每次点按、点击或键盘交互的延迟时间,并根据互动次数选择该网页最差的互动延迟时间(或接近最高延迟时间)作为单个代表性值,以描述网页的整体响应速度 Time to Interactive (TTI):可交互时间,衡量的是从网页开始加载到视觉呈现、其初始脚本(若有)已加载且能够快速可靠地响应用户输入的时间 Total Blocking… Continue Reading 前端性能优化之——预备篇

背景 项目用的组件库是基于Element Plus开发的(后面简称elm),但在使用过程中发现elm的tooltip组件有内存泄漏问题: 进一步排查,发现是Vue-3.3版本下,在内置的transition组件中使用v-if会有问题 定位到这个因素,是因为在element-plus的playground中,使用vue的3.3.x版本存在内存泄漏,而3.5.13版本不存在 具体排查过程就不放了,还有一篇超长内存泄漏blog正在填坑中 因早就嫌弃elm的tooltip组件使用方式不够优雅,遂把目光转向另一个大名鼎鼎的开源Vue3组件库——PrimeVue。 PrimeVue以指令的方式提供了tooltip,比使用组件优雅很多(其实博主已经把elm的tooltip二次封装为指令形式使用了) 正文 问题定位 通过对PrimeVue进行了与elm相同的内存泄漏测试(反复触发tooltip显隐),发现这货的tooltip居然也有内存泄漏问题(如下图),再次印证了草台班子理论。 一番排查后,最终定位到导致泄露的代码: 可以看出只要不触发resize事件,onWindowResize方法的闭包作用域中会一直持有对el的引用 查一查有没有相关的PR: 发现这个问题还没人解决,目测修复也比较简单,故准备给组件库提个PR 梳理逻辑 先来看看PrimeVue的tooltip都有啥 可以看到里面有跟Vue指令定义差不多的生命周期方法和一部分自己的内部方法methods,图中框起来的部分就是产生内存泄漏的主要代码 其中的BaseTooltip是PrimeVue在Vue的ObjectDirective基础上做了二次封装及自己的一些扩展的方法 顾名思义,show方法用于元素的显示,hide方法用于元素隐藏,tooltipRemoval则用于移除tooltip元素,展开看一下内部代码 tooltipActions代码: 到这里我们可以知道: 在触发指令绑定元素的mouseEnter和focus事件时,会调用show方法显示tooltip show方法会调用tooltipActions,而tooltipActions中的会创建window上的resize事件监听器,且不触发就不会被释放,进而导致el一直被该监听器方法创建的闭包作用域引用 看的过程中发现两行不对劲代码,准备顺便一起改了: 修复思路 首先,我们需要能够从外面引用到监听器,以便在其他地方销毁,先将其从tooltipActions里提出来 接下来,需要解决onWindowResize中el参数和this指向的问题,显然应该用bind完成(methods中有bindEvents方法),在bindEvents中添加: 再在tooltipRemoval和unbindEvents里加上销毁监听器的代码 测试效果 构建组件库到本地,link到本地项目测试,反复触发显隐,查看内存快照对比 问题解决,效果符合预期 单元测试,启动! 从项目根目录的package.json中可以看到组件库是有单元测试的 执行一下单元测试,确保没有产生新bug(内存泄漏的单元测试还没研究怎么写,以后整明白了再帮他们补上) 提交PR 提交代码,填写信息,提交PR表单一气呵成 并没有,好久不用Github提代码发现不能用密码登录了,又去建了个token才提上去 贴个PR链接,方便以后查(xin)看(shang) 总结 开发过程中用到window.addEventListener方法时需要谨慎,毕竟像PrimeVue这样的知名开源组件库使用它时也难免出现问题。 给开源项目做了点贡献,自己也进步了一点,感觉良好。

uv Rust编写的python包管理器,无需电脑具备Rust环境就可以安装,性能优秀 支持各大主流OS(mac, Linux, Windows) Zed 最近流行的新代码编辑器,对比VSC性能更好,还支持多人在线编辑 但目前只支持macOS和Linux系统,其中对macOS的支持最完善 按GitHub的介绍来看,后续应该会推出windows和Web的版本,先期待一下 meilisearch 一个高性能搜索引擎,支持各种场景(网站、App等) Loco 用Rust编写的Web应用框架,类似Rails RustScan 一个端口扫描工具,可以用来做网络安全测试和服务监控 第一次知道还有这种工具,长见识了

GetQzonehistory 是时候回顾你的黑历史了,这个项目可以通过获取QQ空间的历史消息列表来获取该账号下发布的所有说说。 快拉上你的小伙伴一起观看吧 : ) Reactive Resume Reactive Resume用于帮助用户创造自己的简历,可以很方便的进行简历的制作、编辑与分享 Docling Docling可以将各种常见的文档格式快速转换为用户需要的形式 LVGL 一个优秀的图形库,提供了创建嵌入式GUI所需的一切 抽象项目名…完全看不出是什么样的项目,但是视觉效果令人舒适 Stable Diffusion web UI Stable Diffusion大家应该很熟悉了,是一个强大的AI画图模型,这个项目提供了可视化界面方便用户调整画图参数 但参数还是很复杂,研究中…

twenty 开发团队经历了传统CRM(Customer Relationship Management)系统的“磨练”之后开发的项目。他们相信一个新的伟大的CRM系统一定会诞生在开源社区当中 Stirling-PDF 一个Web应用,用于对PDF文件进行各种操作(拆分、合并、添加图像、旋转、压缩等) 应用提供了Api,可与外部脚本集成,创建用户自己的工作流 actual 一个用于管理个人财务的工具,适合需要控制个人支出的上班族 但我还是要买那些不会打开的游戏 LocalAI OpenAI的免费开源平替,感兴趣的朋友可以试玩一下 superfile 一个现代的命令行文件管理工具,有各种OS的版本 画风让人想起小时候玩的像素游戏

以后的开源简记都会有一个抽象标题 FingerprintJS FingerprintJS是一个用于识别不同网站访客的服务——即使是匿名访客 这有助于网站防止诈骗、取悦用户以及区分正常用户和“坏”用户 FingerprintJS识别准确率在40%-60% 官方商用版本:Fingerprint Identification,是一个闭源项目,官方承诺99.5%的准确率(因为收集了更多维度的浏览信息) PGlite Postgres的WASM构建版(最近WASM真的很火),可以很方便的实现在浏览器中临时/永久存储数据 支持标准SQL和NoSQL 后端同学:又可以把活丢给前端干啦 Svelte 前端框架又一力(xin)作(keng),带来了比Vue和React更简洁的语法 一个例子就可以看出有多简洁: <script> let count = $state(1); let doubled = $derived(count * 2); let quadrupled = $derived(doubled * 2); function handleClick() { count += 1; } </script> <button onclick={handleClick}> Count: {count} </button> <p>{count} * 2 = {doubled}</p> <p>{doubled}… Continue Reading 【开源简记】第四期——计算机寄术

Rust Course Rust语言圣经! 字里行间都能体会到作者的用心,相信看完项目作者对Rust的描述后你也会对这门语言感兴趣 作者大佬原话: 博主跟项目作者一样认为Rust会越来越受青睐,加之WebAssembly技术逐渐被应用到更多企业中,Rust作为性能可与C/C++比肩的语言…开学吧 Deno 一个JS/TS运行时环境,由于其现代设计和原生TS支持,吸引了许多新项目和开发者 与Node.js对比: 安全性 Node.js: 默认情况下,Node.js 没有任何安全限制,代码可以访问文件系统、网络等所有资源。 安全控制需要通过编写代码来实现,开发者必须自己处理权限管理和安全问题。 Deno: 默认情况下,Deno 运行在一个受限的环境中,不允许访问文件系统、网络、环境变量等资源,除非明确授予权限。 使用命令行参数来授予权限。 模块系统 Node.js: 使用 CommonJS 模块系统,模块通过 require() 进行导入。 依赖管理通过 npm(Node Package Manager),需要 package.json 文件。 Deno: 使用标准的 ES 模块(ESM),模块通过 import 进行导入。 没有中央包管理器,依赖直接通过 URL 导入,可以从任何地方加载模块。 TypeScript 支持 Node.js: 默认不支持 TypeScript,需要通过 Babel 或 TypeScript 编译器(tsc)等工具进行编译。 可以使用… Continue Reading 【开源简记】第三期

immich 厌倦了xx云每月支付?需要避免个人信息流露的风险? immich提供了强大的本地媒体文件管理功能,以及充满现代感的简洁UI与交互方式 Firecrawl Firecrawl提供了一系列工具用于高效进行网页爬取和数据读取,适用于各种需要从网站获取详细和有组织信息的任务 官方示例: playground Hyprland Hyprland基于Wayland协议,为Linux/类Unix系统用户提供高度可定制的动态窗口管理功能 NocoDB 一个将各种数据库转换为智能电子表格的开源软件,官方称自己是Airtable 的开源替代品 题外话:作为前端很喜欢这种现代简约风格的UI(分页器再扁平一点就好了 screenpipe 想回忆起自己在某个时间段做了哪些工作吗?亦或是想快速总结自己今天在计算机上做了什么? screenpipe可以全天候收集电脑中的媒体流信息,并借助AI分析用户想要了解的相关信息 官方示例:examples 打工人摸鱼难度++

LocalSend 基于flutter实现,一个非常实用的开源软件,用于局域网内设备的设备互相传输文件(博主本人也经常使用 NB, 这就去学Dart SuperVision 需要在媒体文件中对各种目标检测和分割模型生成的预测数据进行注释? SuperVision提供了一系列可复用工具来完成这件事情 Kamal Kamal提供了使用Docker在生产环境中部署和管理Web应用所需的一切,旨在简化Docker的部署 官方命令列表: Commands: kamal accessory # Manage accessories (db/redis/search) kamal app # Manage application kamal audit # Show audit log from servers kamal build # Build application image kamal config # Show combined config (including secrets!) kamal deploy # Deploy app to… Continue Reading 【开源简记】第一期

Tailwind介绍 前言 众所周知,CSS是一个相对简单且语法宽松的语言,虽然对开发者友好,但因为它过于“随意”且耦合性强(如height和line-height都会对元素的高度造成影响),如果开发时不做限制,对后续维护的人员来说将会造成“毁灭性打击”。 故为了样式代码的可读性,也为了减少维护人员受到的心理创伤,我们需要对CSS的编写做出限制 BEM命名规范 😀 一般来说,我们编写CSS时类名会遵循BEM命名规范,这种规范的特点是语义化、结构化、遵循开闭原则。参考知乎——CSS之BEM命名规范 “开闭原则”指对修改封闭、对扩展开放,是面向对象思想的重要基本原则 BEM规范存在的问题 😐 语义化的CSS类看似与HTML无关,但在嵌套的CSS选择器(如使用less时)中却反映出了具体的HTML结构。除此以外,CSS里还有很多会被父元素或子元素影响的属性,故将CSS与HTML解耦实际上是很困难甚至无法达成的 在处理两个外表相似的内容时(如下图的作者简介和文章预览),即使样式决策有99%是一致的,我们也很可能需要编写两个语义不同的类来赋予它们样式(如.author-bio和.article-preview),并且这两个类是几乎不可复用的 实际上,可以通过一些CSS预处理器的如 @extend 等功能实现复用,但会对后续维护造成很大的困扰 即便将类拆分到每个组件,使其不基于内容(如.card .button)虽然可以在一定程度上复用这些类,但当组件功能越具体,复用就会越困难(如.dialog-header__button) 给CSS类命名是令人费神的 原子类 原子类,指具有“原子性”的CSS类 在化学反应中“原子”是最小的单位,故“原子性”指具有不可拆解、不可更改的性质 示例: .flex { display: flex; } 上面的.flex就是一个原子类,它的特点就是一个类名对应一个CSS规则,并且类名应该是和规则有关系的 原子类的思想可以说与我们常用的BEM规范“背道而驰”,BEM强调的是语义化与将CSS从HTML的关注点分离(实际上很可能没有分离),而原子类几乎摒弃了语义化,通过使用提供的大量工具类使页面符合最终的设计决策 Tailwind基本概念 Tailwind就是一个采用了CSS原子类和“All in JS”思想的框架 “All in JS”指“HTML in JS”和“CSS in JS”,其中“HTML in JS”就是Vue、React等框架在做的事 框架特点 Tailwind 提供了一组可重用的 CSS 类,可用于几乎任何类型的 UI 组件和布局。 Tailwind… Continue Reading Tailwind 入门