当前位置: 首页 > 科技观察

前端性能优化方法

时间:2023-03-18 15:22:54 科技观察

作者|邱俊涛性能问题是软件开发中的常见问题。我们几乎每个项目在某个时期(往往是后期即将交付时,或者已经上线收到用户反馈后)或多或少都会遇到。本文想从流程和具体技术细节的角度,对软件性能优化中遇到的问题进行总结归类,以期为后续类似场景的开发者提供参考。严格来说,这篇文章并没有太多新内容,我什至在另一篇文章中讨论了一些具体的技术细节。这里我们主要总结一些关于性能优化的常见思路。修改前性能优化的方法是建立、测量、推理、拆解、划分、剥离、延迟、减速。当我们讨论绩效提升时,往往需要先建立一套衡量机制。因为仅凭直觉猜测可能的性能瓶颈是非常低效的,而且往往直觉认为是性能问题的东西实际上可能并不是问题。一旦建立了度量机制,就好像我们的代码被单元测试/集成测试守护着一样,它通常会朝着正确的方向发展。《栗子决》最重要的是定义好指标。即DoD(DefinitionofDone),我们需要回答的问题是:什么是好的性能?达到什么样的标准算改进,达不到就算失败?这从立项的角度来说是非常关键的。如果希望某个页面的性能能够比上一个提升20%,那就是成功了,那么后续的所有开发都可以有的放矢,不会无疾而终。一旦我们定义了什么是促销,就进行单词测试。然后你需要建立相应的测量机制并设置基线。这一步相当于将上一步定义的标准实例化到构建流水线中,让具体的目标可视化,让每一次修改都能看到与目标的差距。比如从发出请求到页面渲染完成(比如检测页面上是否存在某个目标)总共需要3秒。然后我们设置3秒为基线,并围绕这个基线设置测试的上限。与其他测试一样,如果后续代码修改导致页面呈现时间大于基线值,则构建失败。相应的,也可以有bundlesize(压缩后的静态资源大小)、首次渲染时间等指标。有了具体的目标,我们就可以建立相应的测试机制。例如,通过运行yslow或其他灯塔。当我们定义了性能优化成功的意义,并有了相应的反馈机制后,如何去做就会成为最重要的课题。对于这个问题,常用的工具是分析和分类。首先要分析的是“慢”型,是纯性能问题,还是架构问题,还是软件设计问题。纯粹的性能问题往往更具体,也最容易解决。比如你使用性能较低的包作为依赖,只需要替换成性能较好的库即可;或者使用debounce/throttle来减少对函数的频繁调用等。另一大类与纯性能问题相对应的问题可以归结为设计问题(大到软件架构,小到模块之间的耦合/依赖等)。这类问题通常需要引入比较大的修改,但是收益也会很高,从长远来看,也会给代码的可维护性和缺陷率带来不错的回报。因此,此步骤的目标是确定哪些问题可以通过简单的修改来解决,而其他问题则需要进行重大更改。事实是,对于我们之前定义的baseline,可以解决纯粹的性能问题,然后我们就不需要花费大量的工作去做更大的修改了。大纲可以说,性能优化的关键只有推拉。Pusher,不属于我的东西我是绝对不会做的,喜欢做的人都会去做。拖延者,明天能做的事,永远不要碰今天。如果纯粹的最佳实践不能满足要求,我们就需要花更多的时间重构代码的设计来满足性能要求。我们将通过一些具体的例子来详细讨论它。一般来说,我们需要识别代码中的耦合问题,往合理的方向抽象,完成拆分,让各个独立的模块/组件尽可能高内聚低耦合。比如文中讨论的Avatar和Tooltip的例子,头像组件Avartar的核心功能是不包含Tooltip的,两者的耦合度其实很低,可以通过拆分来隔离。修改后的Avatar不再使用Tooltip作为依赖:在其他情况下,组件与其依赖之间的耦合比较紧密,但并非不可替代。比如文中讨论的InlineEdit和InlineDialog场景。这时候可以通过renderprops进行控制反转,让组件不再依赖具体的实现,而是依赖接口。这样所有实现了这个接口的组件都可以做到即插即用,并且可以节省一部分默认依赖(定义在package.json中)的开销。注意这个场景和《再子决》中的场景很像,不同的是拆分组件和当前组件之间有一个隐含的约定:即render传递的所有参数都需要接受。比如上面的例子中,editView并不是完全可以自由定义的,它需要要么接受要么忽略isInvalid、error等参数。在某些场景下,我们可以不提供一个大而全的组件,而是剥离组件适度的附加功能,形成不同的组件,通过不同的入口点导出。这允许用户按需安装。一个典型的例子就是早期版本的lodash。如果用户需要使用partition,还是需要导入整个包:通过不同的入口,你可以只导入你需要的功能:类似的,比如你的按钮组件,你可以提供不同类型的标准按钮、加载按钮或高级按钮,以便用户可以根据需要使用它们。拖动技术以React为例。我们既可以使用原生的React.lazy也可以使用loadable等库来实现按需加载。也就是说,它永远不会被加载到最后一刻(当DOM需要被渲染时)。这在很多场景下都非常有用,尤其是在提升页面初始页面的时候。比如首页的UserProfile里面隐藏了一个巨大的DropdownMenu,我们可以在用户第一次使用的时候加载它。并在加载时提供一个占位符:slowwords这里介绍的最后一个方法是缓存,将耗时的不经常变化的计算结果保存起来,以提高后续的访问速度。这种模式可以是代码级别的,将数据存储在内存中或LocalStorage/SessionStorage中。另一方面,这个原则也适用于架构层面。比如我们引入静态资源存放在CDN,动态资源存放在缓存服务器。仍然以React为例,我们可以使用:使用useMemo来缓存数据使用useCallback来缓存事件响应函数使用memo来缓存静态组件(尤其是叶子节点)例如,对叶子节点Toggle使用API级缓存后,它可以写成这样:另外需要注意的是,使用额外的API比如useMemo或者useCallback本身也是有成本的。在实际场景中,需要结合上述的试字策略来保证实际数字的提升,而不是迷信API。总结本文总结了性能优化中的一些常用方法和模式。在开始实施之前,我们需要确定成功的性能优化的定义。然后我们需要设置一个baseline和一个matchingtest,这样我们就可以随时知道我们的优化是否有效,或者与预期的差距,从而时刻保持目标的清晰。接下来,需要对性能问题进行初步分析和分类,比如架构缺陷,或者没有采用微代码层面的最佳实践。接下来,我们讨论几类常见的优化方法。比如按照耦合度拆分,按照复杂/差异化程度拆分,使用接口实现依赖倒置,缓存的使用等等。这些具体的做法在不同的技术栈上可能会有不同的具体做法(针对例如Angular中可能有偷懒的对应,或者Vue中可以使用类似的技术实现备忘录等),但这些思路都比较笼统。可以应用在类似的场景中。原文链接:前端性能优化方法(qq.com)