怎么才能知道网站是谁做的,wordpress 动画,哈尔滨建设网站的免费咨询,怎么注册公司需要什么手续简介#xff1a; 性能优化是一个体系化、整体性的事情#xff0c;印刻在项目开发环节的各个细节中#xff0c;也是体现技术深度的大的战场。文章以Quick BI的复杂系统为背景#xff0c;详细介绍性能优化的思路和手段#xff0c;以及体系化的思考。
一直以来#xff0c;性…简介 性能优化是一个体系化、整体性的事情印刻在项目开发环节的各个细节中也是体现技术深度的大的战场。文章以Quick BI的复杂系统为背景详细介绍性能优化的思路和手段以及体系化的思考。
一直以来性能都是技术层面不可避开的话题尤其在中大型复杂项目中。犹如汽车整车性能追求极速的同时还要保障舒适性和实用性而在汽车制造的每个环节、零件整合情况、发动机调校等等都会最终影响用户体感以及商业达成如下图性能对收益的影响。 性能优化是一个体系化、整体性的事情印刻在项目开发环节的各个细节中也是体现技术深度的大的战场。下面我将以Quick BI的复杂系统为背景深扒整个性能优化的思路和手段以及体系化的思考。 如何定位性能问题 通常来讲我们对动画的帧率是比较敏感的16ms内但如果出现性能问题我们的实际体感可能就一个字“慢”但这并不能为我们解决问题提供任何帮助由此我们需要剖析这个字背后的整条链路。 上图是浏览器通用的处理流程结合我们的场景我这里抽象成以下几个步骤 可以看出主要的耗时阶段分为两个
阶段一资源包下载Download Code
阶段二执行 取数Script Execution Fetch Data
如何深入这两个阶段我们一般会用以下几个主要的工具来分析
Network
首先我们要使用的一个工具是Chrome的Network它能帮助我们初步定位瓶颈所在的环节 如图示例在Network中可以一目了然看到整个页面的加载时间Finish、加载资源大小、请求数量、每个请求耗时及耗时点、资源优先级等等。上面示例可以很明显看出整个页面加载的资源很大接近了30MB。
Coverage代码覆盖率
对于复杂的前端工程其工程构建的产物一般会存在冗余甚至未被使用的情况这些无效加载的代码可以通过Coverage工具来实时分析 如上图示例可以看到整个页面28.3MB其中19.5MB都未被使用执行其中engine-style.css文件的使用率只有不到0.7%
资源大图
刚才我们已经知道前端资源的利用率非常低那么具体是哪些无效代码被引入进来了这时候我们要借助webpack-bundle-analyzer来分析整个的构建产物产物stats可以通过webpack --profile --jsonstats.json输出 如上例结合我们当前业务可以看到构建产物的问题
第一初始包过大common.js
第二存在多个重复包momentjs等
第三依赖的第三方包体积过大 模块依赖关系
有了资源构建大图我们也大概知道了可优化的点但在一个系统中成百上千的模块一般都是通过互相引用的方式组织在一起打包工具再通过依赖关系将其构建在一起比如打成common.js单个文件想要直接移除掉某个模块代码或依赖可能并非易事由此我们可能需要一定程度抽丝剥茧借助工具理清系统中模块的依赖关系再通过调整依赖或加载方式来作优化 上图我们使用到的是webpack官方的analyse工具其他工具还有webpack-xrayMadge只需要将资源大图stats.json上传即可得到整个依赖关系大图
Performance
前面讲到的都是和资源加载相关的工具那么在分析 “执行 取数” 环节我们使用什么Chrome提供了非常强大的工具Performance 如上图示例我们可以至少发现几个点主流程串化、长任务、高频任务。 如何优化性能
结合刚才提到的分析工具刚才提到的 “资源包下载”、“执行 取数” 两个大的阶段我们基本上已经覆盖到其根本问题和解法也在不断的分析中逐步有了思路这里我将结合我们这里的场景给出一些不错的优化思路和效果
大包按需加载
要知道前端工程构建打包如webpack一般是从entry出发去寻找整棵依赖树直接依赖从而根据这棵树产出多个js和css文件bundle或trunk而一个模块一旦出现在依赖树中那么当页面加载entry的时候同时也会加载该模块。
所以我们的思路是打破这种直接依赖针对末端的模块改用异步依赖方式如下 将同步的import { Marker } from antv/l7改为异步这样在构建时被依赖的Marker会形成一个chunk仅在此段代码执行时按需该thunk才被加载从而减少了首屏包的体积。
然而上面方案会存在一个问题构建会将整个antv/l7作为一个chunk而非Marker部分代码导致该chunk的TreeShaking失效体积很大。我们可以使用构建分片方式解决 如上先创建Marker的分片文件使之具备TreeShaking的能力再在此基础上作异步引入。
下方是我们优化后的流程对比结果 这一步我们通过按需拆包异步加载节省了资源下载时间和部分执行时间
资源预加载
其实我们在分析阶段已经发现一个“主流程串化”的问题js的执行是单线程但浏览器实际上是多线程运行的这里面就包括异步请求fetch等所以我们进一步的思路是把取数Fetch Data与资源下载通过多线程并行。
按照当前现状接口取数的逻辑一般是耦合在业务逻辑或数据处理逻辑中的所以解耦与UI、业务模块等解耦的步骤必不可少将纯粹的fetch请求及少量处理逻辑剥离出来放到优先级更高的阶段来发起请求。那么放到什么地方呢我们知道浏览器对资源的处理是有优先级的正常按如下顺序
HTML/CSS/FONTPreload/SCRIPT/XHRImage/Audio/VideoPrefetch
要做到资源拉取 和 发起取数并行就有必要把取数提前到第1优先级HTML解析完毕后立即执行而非等待SCRIPT标签资源加载执行过程中发起请求我们的流程会变成如下 需要特别注意一点由于JS的执行是串行发起取数的那段逻辑必须要先于主流程逻辑执行并且不能放到nextTick如使用setTimeout(() doFetch())否则主流程会一直占用CPU时间使得请求无法发出
主动任务调度
浏览器对资源也有优先级策略但它并不知道业务层面的我们到底想要哪些资源先加载/执行哪些资源后加载/执行所以我们跳出来看若把整个业务层面的资源加载执行/取数流程拆成一个一个小的任务这些任务全权由我们自己来控制其打包粒度、加载时机、执行时机是不是意味着能最大化利用CPU时间和网络资源了
答案是肯定的不过一般对于简单的项目浏览器本身的调度优先级策略已经足够满足需要但如果针对大型复杂项目要做的相对极致的优化就有必要引入“自定义任务调度”方案了。
以Quick BI为例我们的前期目标是让首屏主要内容展现更加快速。那么从资源加载、代码执行、取数层面是应该根据我们业务优先级作CPU/网络分配的比如我希望“卡片的下拉菜单”在首屏主要内容展示完毕后或CPU空闲时才开始加载即降低优先级更甚至在用户鼠标移入卡片中时又希望它提高优先级立即开始加载并展示。如下 这里我们封装了一个任务调度器其目的是可以声明一段逻辑在其某个依赖Promise完成后开始执行。我们的流程图变化如下 黄色区块代表 作优先级降级处理的部分模块其帮助减少了整个首屏时间
TreeShaking
上面讲方法大多从优先级出发其实在前端工程化日益复杂的时代中大型项目已超几十万行代码诞生了一个较为智能的优化方案用于减少包大小其思想很简单工具化分析依赖关系将没有被引用到的代码从最终产物中剔除掉。
听起来很酷实际用起来也非常不错但这里想讲一些很多其官网也不会提到的点 --- TreeShaking经常失效的情况
副作用
副作用Side Effects通常表达的是对全局如window对象等或环境会产生影响的代码。 如图示例b代码看似未被使用但其文件中存在console.log(b(1))这样的代码webpack等打包工具不敢轻易移除它所以它会被照常打入。
解决方法
在package.json 或 webpack配置中明确指定哪些代码具备副作用例如sideEffects: [“**/*.css”]无副作用的代码将被移除
IIFE类代码
IIFE即会被立即执行的函数表达式Immediately invoked function expression 如图这类型的代码会导致TreeShaking失效
解决方法
三个原则
[避免]立即执行的函数调用[避免]立即执行的new操作[避免]立即影响全局的代码
懒加载
我们在“按需加载”处提到过异步import来做拆包会导致TreeShaking失效这里再进一步说明一下另外一个case 如图由于index.ts同步import了bar.ts中的sharedStr然后在某个地方又同时异步import(./bar)这种情况下会同时导致两个问题
TreeShaking失效unusedStr会被打入异步懒加载失效bar.ts会和index.ts打入到一起
当代码量达到一定量级N个人协同开发就很容易出现这个问题
解决方法
[避免]同步和异步import同个文件
按需策略Lazy
其实前面有讲到一些按需加载的方案这里我们适当延伸一下既然资源包的加载可以做到按需是否某个组件的渲染可以按需某个对象实例的使用可以按需某个数据缓存的生成也可以按需
懒组件LazyComponent 如图PieArc.private.ts对应一个复杂的React组件PieArc通过makeLazyComponent封装成默认懒加载的组件只有在代码执行到此处时组件才会加载并执行。甚至还可以通过第二个参数deps申明依赖待依赖promise完毕时才加载和执行。
懒缓存LazyCache
懒缓存用于这种场景需要在任何地方使用到数据流或其他可订阅数据中的某个数据经过转换后的结果且仅在使用的那一刻才进行转换 懒对象LazyObject
懒对象意即该对象只有在被使用的时候属性/方法被访问、修改、删除等等才会被实例化 如图globalRecorder被引入时其并未实例化仅当调用globalRecorder.record()时进行实例化
数据流节流渲染
中大型项目中为了方便状态管理通常会使用到数据流的方案如下流程 store中存储的数据通常偏原子化粒度非常小比如state中有a、b、c ...等N个原子属性某个组件依赖这N个属性来作UI渲染假设N个属性会在不同的ACTION下被改变且这些改变均在16ms内发生那么若N20则16ms内1帧会有20次View更新 这显然会引发非常大的性能问题由此我们需要对短时间的ACTION量作一个缓冲节流待20次ACTION状态改变完毕后仅进行1次View更新如下 此方案在Quick BI以redux中间件的形式发挥作用在复杂频繁数据更新场景起到了不错的效果 思考
“君子以思患而豫防之”当我们回过头去看看出现的这些性能问题在架构设计、编码阶段是可以避免掉80%以上的20%的则可以“空间时间置换策略”等方式去平衡。所以最佳的性能优化方案是在于我们对每一段代码质量的执着是否考虑到了这样的模块依赖关系可能带来的构建产物体积问题是否考虑到了这段逻辑可能的执行频次是否考虑到了随着数据增长空间或CPU占用的可控性等等。性能优化没有银弹作为技术人需要内修于心熟知底层原理把对性能的执念植入本能思考当中方为银弹。
原文链接
本文为阿里云原创内容未经允许不得转载。