背景字节各类业务拥有众多用户群,作为字节前端性能监控 SDK , 自身若存在性能问题,则会影响到数以亿计的真实用户的体验 。所以此类 SDK 自身的性能在设计之初,就必须达到一个非常极致的水准 。
与此同时,随着业务不断迭代,功能变得越来越多,对监控的需求也会变得越来越多 。例如,今天 A 业务更新了架构,想要自定义性能指标的获取规则 , 明天 B 业务接入了微前端框架,需要监控子应用的性能 。在解决这些业务需求的同时 , 我们会不断加入额外的判断逻辑、配置项 。同时由于用户的电脑性能、浏览器环境的不同,我们又要解决各种兼容性问题,加入 polyfill 等代码,不可避免地造成 SDK 体积膨胀,性能劣化 。那么我们是如何在需求和功能不断迭代的情况下,持续追踪和优化 SDK 的体积和性能的呢?
SDK 体积优化通常而言,体积的优化是最容易拿到收益的一项 。
由于监控 SDK 通常作为第一个脚本被加载到页面中,体积的膨胀不仅会增加用户的下载时间,还会增加浏览器解析脚本的时间 。对于体积优化 , 我们可以从宏观和微观两个角度去实现 。
微观上,我们会去尽可能去精简所有的表达,剥离冗余重复代码,同时尽可能减少以下写法的出现:
1. 过多的 class 和过长的属性方法名
Class 的定义会被转换成 function 声明 + prototype 赋值,以及常用代码压缩工具无法对 object 属性名压缩,过多的面向对象写法会让编译后的 js 代码体积膨胀得非常快 。例如下列代码
class ClassWithLongName {methodWithALongLongName() {}}
经过 ts 转换后会变成
var ClassWithLongName = /** @class */ (function () {function ClassWithLongName() {}ClassWithLongName.prototype.methodWithALongLongName = function () { };return ClassWithLongName;}());
压缩后代码为
var ClassWithLongName=function(){function n(){}return n.prototype.methodWithALongLongName=function(){},n}();
可以看到以上长命名都无法被压缩
如果使用函数式编程来代替面向对象编程,能够很好的避免代码无法被压缩的情况:
function functionWithLongName() {return function MethodWithALongLongName(){}}
经过压缩后变成
function n(){return function(){}}
相较于 class 的版本,压缩后的代码减小了50%以上 。
2. 内部函数传参使用数组代替对象
原理同上,对象中的字段名通常不会被代码压缩工具压缩 。同时合理使用 TS named tuple 类型可以保证代码可维护性 。
function report(event, {optionA, optionB, optionC, optionD}: ObjectType){}
改为
function report(event, [optionA, optionB, optionC, optionD]: NamedTupleType){}
3. 在不需要判断 nullable 时,尽可能避免?.????=
等操作符的出现 。同理,尽可能避免一些例如 spread 操作符、generator 等新语法 , 这些语法在编译成 es5 后通常会引入额外的 polyfill 。TS 会将这些操作符转换成非常长的代码,例如a?.b
会被转换成:
a === null || a === void 0 ? void 0 : a.b
过多的 nullish 操作符也是代码体积增加的一个原因 。
当然,以上只列举了部分体积优化措施 , 还有更多优化方法要结合具体代码而议 。对于我们的前端监控 SDK,为了性能和体积是可以牺牲一些开发体验的 , 并且由于使用 TS 类型系统,并不会对代码维护增加很多负担 。
从宏观上,我们应该思考如何减少 SDK 所依赖的模块,减少产物包含的内容,增加产物的“信噪比” , 有以下几个方式:
- 拆分文件
2. 尽可能避免 polyfill 的使用
polyfill 会显著增加产物体积,我们尽可能不使用存在兼容性的方法 。甚至在不需要兼容低端浏览器环境时 , 我们可以不使用 polyfill 。
3. 减少重复的常量字符串的出现次数
对于多次重复出现的常量字符串,提取成公共变量 。例如
a.addEventListener('load', cb)b.addEventListener('load', cb)c.addEventListener('load', cb)
我们可以将 addEventListener
和 load
提取公共变量:let ADD_EVENT_LISTENER = 'addEventLister'let LOAD = 'load'a[ADD_EVENT_LISTENER](LOAD, cb)b[ADD_EVENT_LISTENER](LOAD, cb)c[ADD_EVENT_LISTENER](LOAD, cb)
推荐阅读
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 使用 nvm 对 node 进行版本管理
- 四 Selenium4.0+Python3系列 - 常见元素操作(含鼠标键盘事件)
- iQOO8系列配置_iQOO8系列参数详情
- 30 《吐血整理》高级系列教程-吃透Fiddler抓包教程-Fiddler如何抓取Android7.0以上的Https包-番外篇
- 从0搭建vue3组件库: 如何完整搭建一个前端脚手架?
- 树的邻接矩阵、双亲孩子表示法…… C++ 不知树系列之初识树
- .NET Core C#系列之XiaoFeng.Data.IQueryableX ORM框架
- flutter系列之:永远不用担心组件溢出的Wrap
- 荣耀x20评测_荣耀x20评测表现
- 抛砖系列之redis监控命令