前端性能优化是每个前端工程师都关注的话,题很多人都知道一些基础的优化技巧,比如压缩代码图片懒加载CDN缓存代码分割等等这些是前端性能优化的基本功每个前端都应该掌握。
但是,还有一些进阶的优化技巧可能很多人不知道,或者没有注意到这些技巧,虽然不如基础技巧,那么广为人知,但是在某些场景下能带来很大的性能提升特别是对于追求极致性能的网站来说这些进阶技巧能帮你把性能提升到新的高度。
今天想分享一些前端性能优化的进阶技巧包括渲染性能JS执行优化网络优化内存优化等等这些技巧你可能不知道希望能帮大家把网站性能提升到新的高度。
一、渲染性能优化
渲染性能是前端性能的重要方面特别是对于交互复杂动画多的网站渲染性能直接影响用户体验这里分享几个进阶的渲染优化技巧。
技巧1:用contain属性限制重排重绘范围:
CSS的contain属性是一个比较新的CSS属性能限制元素的重排重绘范围提升渲染性能contain属性告诉浏览器这个元素的变化不会影响元素外部的布局和渲染,所以浏览器可以只重新渲染这个元素内部不用重新渲染整个页面大大提升性能。
contain属性有几个值:
contain: layout:元素内部的布局变化不会影响外部布局。contain: paint:元素内部的绘制不会超出元素边界也不会影响外部绘制。contain: size:元素的尺寸不依赖内部内容,也就是说元素大小固定不会,因为内部内容变化而变化。contain: style:元素内部的样式变化不会影响外部样式。contain: content:相当于layout + paint + size最常用。contain: strict:相当于layout + paint + size + style最严格。
对于独立的组件,比如弹窗卡片列表项等等可以加contain: content这样组件内部的变化不会影响外部渲染性能提升明显特别是组件多变化频繁的页面效果更明显。
目前ChromeFirefox都支持contain属性Safari支持稍晚,但是可以作为渐进增强支持的浏览器享受性能提升不支持的也不影响功能。
技巧2:用will-change提示浏览器优化:
will-change属性能提示浏览器这个元素即将发生变化让浏览器提前做好优化准备,比如把元素提升为独立的合成层开启GPU加速等等这样变化发生的时候,能更流畅不会卡顿。
will-change的用法很简单,比如will-change: transform提示浏览器这个元素的transform属性即将变化浏览器会提前优化transform相关的渲染。
但是will-change不能滥用,因为它会占用额外的内存和GPU资源,如果给所有元素都加will-change反而会影响性能正确的用法是只给即将发生变化的元素加变化结束后移除,比如鼠标悬停的元素动画开始前加will-change动画结束后移除。
另外will-change不要用all值这样浏览器会对所有属性都做优化浪费资源应该只指定真正会变化的属性,比如transformopacity等等。
技巧3:用transform和opacity做动画避免重排:
这个技巧很多人知道,但是值得再强调一下做动画的时候,尽量用transform和opacity属性不要用topleftwidthheightmarginpadding等等会触发重排(Layout)的属性,因为重排的代价很大会导致整个页面重新计算布局性能差而transform和opacity只触发合成(Composite)不触发重排和重绘性能好很多能达到60fps的流畅动画。
如果确实需要改变元素的位置和大小用transform: translate()和transform: scale()来代替top/left和width/height效果一样,但是性能好很多。
另外做动画的元素最好加transform: translateZ(0)或者will-change: transform开启GPU加速提升动画性能。
技巧4:避免布局抖动(Layout Thrashing):
布局抖动(Layout Thrashing)是指在JavaScript中频繁地交替读取和写入布局相关的属性导致浏览器被迫多次同步计算布局性能严重下降,比如下面的代码:
// 错误的写法会导致布局抖动
for (let i = 0; i < elements.length; i++) {
const width = elements[i].offsetWidth; // 读取布局
elements[i].style.width = (width * 2) + 'px'; // 写入布局
}这段代码每次循环都先读取offsetWidth(触发布局计算)然后写入width(使布局失效)下一次循环又读取offsetWidth(又触发布局计算)这样反复多次同步布局计算性能很差。
正确的写法是把读取和写入分开先批量读取所有需要的布局属性缓存起来,然后再批量写入这样浏览器只需要计算一次布局性能好很多:
// 正确的写法先批量读取再批量写入
const widths = [];
for (let i = 0; i < elements.length; i++) {
widths.push(elements[i].offsetWidth); // 批量读取
}
for (let i = 0; i < elements.length; i++) {
elements[i].style.width = (widths[i] * 2) + 'px'; // 批量写入
}另外也可以用requestAnimationFrame把写入操作放到下一帧执行避免和,读取操作交替也能避免布局抖动。
布局抖动是很多前端容易忽略的性能问题特别是在操作DOM比较多的页面很容易出现导致页面卡顿注意避免能大幅提升性能。
二、JS执行优化
JS执行性能也是前端性能的重要方面特别是JS重的应用这里分享几个进阶的JS执行优化技巧。
技巧5:用Web Worker处理计算密集型任务:
Web Worker能把计算密集型的任务放到后台线程执行不阻塞主线程保持页面响应这是提升JS性能的重要技巧,但是很多人没有充分利用。
很多前端遇到计算密集型的任务(比如大数据处理复杂计算图片处理等等)还是在主线程执行导致页面卡顿用户体验差其实这些任务完全可以放到Web Worker里执行主线程只负责UI和交互页面就会很流畅。
Web Worker的用法也不复杂创建一个Worker文件在里面处理计算任务通过postMessage和主线程通信就行现在也有很多库能简化Web Worker的使用,比如workerizecomlink等等能让你像调用普通函数一样调用Worker里的方法非常方便。
注意Web Worker里不能访问DOM也不能用window对象的很多方法只能做纯计算和网络请求,所以适合处理计算密集型的纯逻辑任务不适合操作DOM。
技巧6:用requestIdleCallback处理非紧急任务:
requestIdleCallback是一个比较新的API能把非紧急的任务放到浏览器空闲的时候,执行不影响关键交互和动画保持页面响应。
比如统计上报预加载非关键资源缓存数据分析等等这些非紧急的任务都可以用requestIdleCallback放到浏览器空闲时执行这样不会和用户交互抢主线程资源页面更流畅。
requestIdleCallback的用法:
requestIdleCallback((deadline) => {
while (deadline.timeRemaining() > 0 && tasks.length > 0) {
doTask(tasks.shift());
}
if (tasks.length > 0) {
requestIdleCallback(arguments.callee);
}
});callback会收到一个deadline对象有timeRemaining()方法返回当前空闲时间还剩多少毫秒我们可以在,空闲时间内处理任务时间用完了就停止等下一次空闲再继续这样不会阻塞主线程。
注意requestIdleCallback的兼容性目前ChromeFirefox支持Safari不支持,所以需要加polyfill或者用setTimeout降级,另外requestIdleCallback不保证一定会执行,如果浏览器一直忙可能会延迟,所以不要把紧急的任务放到这里。
技巧7:防抖和节流的正确使用:
防抖(debounce)和节流(throttle)是前端常用的性能优化技巧,但是很多人用得不对,或者用得不够这里再强调一下正确的使用场景。
- 防抖(debounce):在事件触发后等待一段时间,如果这段时间内没有再触发才执行回调,如果又触发了就重新计时适合搜索框输入联想(用户输入停止后才发请求)窗口resize(窗口大小调整停止后才重新计算布局)等等避免频繁触发回调。
- 节流(throttle):在一段时间内只执行一次回调,不管触发多少次适合scroll事件(滚动时频繁触发节流后每隔一段时间执行一次)mousemove事件按钮频繁点击等等保证回调执行频率不超过阈值。
很多人在scrollresizeinputmousemove等高频事件上没有加防抖或节流导致回调执行太频繁主线程被占满页面卡顿加上防抖或节流后性能提升明显。
另外现在浏览器原生支持CSS的scroll-behavior: smooth和,passive事件监听器也能提升滚动性能配合防抖节流效果更好。
技巧8:用事件委托减少事件监听器:
事件委托(Event Delegation)是利用事件冒泡机制把子元素的事件监听器绑定到父元素上通过判断事件目标来处理不同子元素的事件这样只需要一个事件监听器就能处理所有子元素的事件大大减少事件监听器的数量提升性能和内存使用。
比如一个列表有1000个列表项每个列表项都要绑定click事件,如果每个都绑定一个监听器就需要1000个监听器占用很多内存初始化也慢用事件委托只需要在列表容器上绑定一个click事件通过e.target判断点击的是哪个列表项就能处理所有列表项的点击事件只需要一个监听器性能好很多。
事件委托还能自动处理动态添加的元素不需要每次添加元素都重新绑定事件非常方便对于动态内容多的页面特别适合。
三、网络优化
网络优化也是前端性能的重要方面这里分享几个进阶的网络优化技巧。
技巧9:用preconnect和dns-prefetch提前建立连接:
对于第三方域名的资源(比如CDNAPI分析服务等等)可以用preconnect和,dns-prefetch提前建立连接减少请求时的连接时间。
dns-prefetch:只提前解析DNS把域名转成IP地址耗时少适合很多第三方域名。preconnect:提前建立完整的连接包括DNS解析TCP握手TLS握手耗时多,但是能节省更多请求时间适合关键的第三方域名。
用法:
<link rel="dns-prefetch" href="//api.example.com">
<link rel="preconnect" href="//cdn.example.com" crossorigin>注意preconnect不要滥用,因为建立连接会占用资源只对关键的马上要用的第三方域名用其他的用dns-prefetch就够了,另外跨域的preconnect需要加crossorigin属性,否则浏览器会建立两次连接。
技巧10:用preload和prefetch预加载关键资源:
preload和prefetch能提前加载资源提升性能,但是很多人分不清两者的区别用得不对。
preload:预加载当前页面马上需要的关键资源(比如字体关键CSS首屏图片等等)浏览器会优先加载这些资源优先级高适合当前页面关键资源。prefetch:预获取下一个页面可能需要的资源浏览器在空闲时加载优先级低适合下一个页面可能用到的资源,比如用户可能点击的下一个页面的JSCSS等等。
用法:
<!-- 预加载当前页面关键资源 -->
<link rel="preload" href="critical.css" as="style">
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>
<!-- 预获取下一个页面可能需要的资源 -->
<link rel="prefetch" href="next-page.js">注意preload也不要滥用只对当前页面真正关键的资源用,否则会浪费带宽影响其他资源加载,另外preload的资源必须在页面中使用,否则浏览器会警告浪费了预加载。
技巧11:用HTTP/2和服务器推送:
HTTP/2相比HTTP/1.1有很多性能优势,比如多路复用头部压缩服务器推送等等能大幅提升页面加载性能现在大部分浏览器和服务器都支持HTTP/2应该尽量开启。
特别是多路复用能在一个TCP连接上并行传输多个请求不用像HTTP/1.1那样建立多个连接也不用排队等待请求完成对于资源多的页面性能提升明显。
服务器推送(Server Push)是HTTP/2的特性能服务器主动把客户端还没请求的资源推过去,比如客户端请求HTML服务器知道这个HTML需要某个CSS和JS就主动把这些资源一起推给客户端减少请求往返提升加载速度。
不过服务器推送要谨慎使用只推送客户端真正需要的关键资源不要推送客户端已经缓存的资源,否则会浪费带宽反而影响性能,另外服务器推送的支持和配置也比较复杂需要根据实际情况使用。
四、内存优化
内存优化也是前端性能的重要方面特别是长时间运行的应用(比如单页应用管理后台等等)内存泄漏会导致页面越来越卡甚至崩溃这里分享几个内存优化技巧。
技巧12:及时清理事件监听器和定时器:
内存泄漏最常见的原因之一就是事件监听器和定时器没有及时清理,比如组件销毁的时候,没有移除绑定的事件监听器没有清除定时器导致这些回调函数和相关的DOM引用一直留在内存里无法被垃圾回收内存占用越来越大页面越来越卡。
所以在组件销毁的时候,一定要及时清理事件监听器(removeEventListener)和定时器(clearTimeoutclearInterval)取消未完成的AJAX请求等等确保没有残留的引用能被垃圾回收。
特别是用框架(ReactVue等等)的时候,要利用组件的生命周期钩子(比如React的componentWillUnmountVue的beforeDestroy)在组件销毁前清理资源很多人容易忽略这个导致内存泄漏。
技巧13:用WeakMap和WeakSet避免内存泄漏:
WeakMap和WeakSet是ES6新增的数据结构和普通的MapSet不同它们的键(WeakMap)或值(WeakSet)是弱引用不会阻止垃圾回收,如果对象没有其他引用了就会被垃圾回收不会,因为在WeakMap/WeakSet里就留在内存里。
这对于缓存和关联数据很有用,比如我们想给DOM元素关联一些数据用普通的Map的话DOM元素移除了,但是Map里还引用着DOM元素导致DOM元素无法被垃圾回收内存泄漏用WeakMap的话DOM元素移除了没有其他引用了就会被垃圾回收WeakMap里的对应数据也会自动消失不会内存泄漏。
所以在需要用对象作为键缓存数据的场景优先用WeakMap而不是普通的Map能避免内存泄漏同样需要存对象集合的场景用WeakSet而不是Set。
技巧14:长列表用虚拟滚动:
长列表是前端常见的性能问题特别是成千上万条数据的列表,如果全部渲染DOM节点会占用大量内存渲染也慢页面卡顿这时候用虚拟滚动(Virtual Scroll)是很好的解决方案。
虚拟滚动的原理是只渲染可视区域内的列表项不可视区域的列表项不渲染用占位元素撑高度滚动的时候,动态替换可视区域的列表项这样,不管列表有多少条数据DOM节点数量都,只有可视区域的那几十个内存占用少渲染快性能好。
现在有很多成熟的虚拟滚动库,比如react-windowreact-virtualizedvue-virtual-scroller等等用起来很方便对于长列表场景强烈推荐使用能大幅提升性能。
五、写在最后
以上就是我分享的一些前端性能优化的进阶技巧包括渲染性能优化JS执行优化网络优化内存优化等等这些技巧可能很多人不知道,或者没有注意到,但是在追求极致性能的场景下能带来很大的提升。
前端性能优化是一个系统工程基础技巧重要进阶技巧也重要需要根据项目的实际情况综合运用才能达到最好的效果,而且性能优化不是一次性的工作是持续的过程需要持续监控测量优化才能保持好的性能。
另外性能优化要数据驱动不要凭感觉优化先测量找到瓶颈再针对性优化优化后再测量验证效果不要盲目优化做无用功用ChromeDevToolsLighthouseWebPageTest等工具测量和分析性能找到真正的瓶颈。
希望这些进阶技巧能帮大家把网站性能提升到新的高度给用户更好的体验。
最后用一句话结束这篇文章:"性能优化没有最好,只有更好基础技巧是基本功进阶技巧是杀手锏综合运用持续优化才能给用户极致的体验。"
愿大家的网站都能又快又流畅用户体验棒棒的。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录