前端性能优化在中高级的面试中出现概率相当之高。不了解前端的性能优化并不会影响你成为一名合格的前端工程师。但它是每一个优秀的前端工程师多多少少都需要知道的东西。
希望以下内容对你后面找工作/换工作有帮助。
第一种,启用前端缓存
所谓的前端缓存,其实就是http缓存,通过(强缓存/协商缓存)等方式让计算机直接从缓存中读取静态资源,从而实现节约宽带,提高响应速度,减少服务器压力等优化。
参考案例:
中高级前端工程师都需要熟悉的技能--前端缓存 - 掘金 (juejin.cn)
一文!彻底弄懂前端缓存_zz_jesse的博客-CSDN博客
第二种,开启GZIP压缩
这主要针对工程化项目,如react/vue等。
常规情况下前端部署所需要的dist包中会有一些静态文件(如js,css,图片文件)。这些静态文件会在项目初始化后续某个动作下被加载。出于体积大小的不同,加载速度也不一样。有些文件比较大,加载所需时间相对较长,针对文件加载慢的情况。
我们可以采用一些压缩方案,让这些静态文件的体积尽量变小。这样,就可以相对的节约宽带,而因为这些文件的变小,对这些的静态文件的加载的速度也会得到提升,客户端也可以尽快响应给用户一个良好的体验。
gzip有着比zip更优秀的压缩算法,可以有效的减少文件的大小。
每个框架配置gzip的方法都不太一样,没有标准答案。但是大概的流程都是一样的。
- 下载compression-webpack-plugin插件
- 配置到webpack中
- 通知后端开启gzip
完成
参考案例
react中的umi框架开启gzip.com
vue开启gzip压缩.com
第三种,使用函数节流和函数防抖
节流和防抖是经典的优化方案。
节流和防抖都是将我们大量重复多余的操作进行合并,以达到减少客户端或服务端压力,提高运算速度,减少http请求等效果。
参考文章:
前端性能优化篇之函数节流和函数防抖和他们的区别 - 掘金 (juejin.cn)
函数节流与函数防抖_函数节流和函数防抖_何故逸的博客-CSDN博客
第四种,异步加载script文件或将script文件放在最后加载
浏览器在下载和解析script文件的时候会停止html的解析和 CSSOM 的构建。
所以,在以前我们通常喜欢把< script >标签放在html的最后面。
当然,不想将< script >标签放在后面又不想让script的下载和解析影响html的渲染,也有方案。在script标签中加上defer属性即可。
script标签的defer属性可以让script异步加载并在DOM构建完成和CSS渲染完毕之后再执行。
写文章 - script标签中的async和defer标签到底是干什么的?详解 - 掘金 (juejin.cn)
HTML script defer 属性 | 菜鸟教程 (runoob.com)
第五种,减少重排和重绘
重排和重绘是浏览器中相对比较耗时的动作。尤其是重排。
重绘不一定会引起重排。重排一定会导致重绘。
浏览器上我们所能看见的元素。当它们的位置发生改变的时候,并不是流动的。而是先被擦除,再重新生成。这就像画画,当画上的某一个单位需要改变位置,我们无法直接把这个单位直接进行移动,只能先将其擦除,然后在指定的位置重新画一个。
元素改变位置,浏览器会先在指定位置上构建该元素的dom(重排)(注意这里没有渲染),然后在对该元素进行渲染(比如background,color)(重绘)。
元素在位置上的改变属于重排,非位置上的改变基本属于重绘(不绝对)。
比如一个原本红色背景的div,如果仅仅改变背景为蓝色的话,那么只会触发重绘,并不会触发重排。(因为位置没有变,只有CSS改变)
重绘触发场景
- background的改变
- color的改变
- visibility:hidden
- css3的translate
- color, border-style, border-radius, visibility, text-decoration, background, background-image, background-position, background-repeat, background-size,outline-color, outline-style, outline-width, box-shadow
- ...
重排的触发场景
- 删除或者新增一个节点元素
- 元素位置的改变,比如float,position,overflow,display等等
- 元素尺寸的改变,比如margin,padding,height,width等等
- 初始化构建DOM树的时候
- 窗口尺寸的变化 也就是resize事件发生的时候
- 填充内容的改变(内容撑大了某一个节点,内容改变,包含它的节点大小自然跟随调整。)
- 读取某一个元素的时候,比如offsetLeft,offsetTop,offsetHeight,offsetWidth, clientTop,clientLeft,clientWidth,clientHeight, scrollTop,scrollLeft,scrollWidth,scrollHeight, width,height等等
- ...
参考文章:
前端性能优化篇之重绘和回流 - 掘金 (juejin.cn)
重排(reflow)和重绘(repaint) - 掘金 (juejin.cn)
第六种,使用服务端渲染
如果使用服务端渲染(SSR)的话,首先首屏加载速度会有显著的提升(因为SSE只需要加载首页一个页面)。并且对SEO也很友好。
当然它也有弊端:页面数据更容易被爬。服务器压力会变大。
nuxt.js和next.js等都是比较流行SSR框架。
nuxt官网:Nuxt.js 中文网 (nuxtjs.cn)
next中文网:Next.js 中文网 (nuxtjs.cn)
参考文章:
服务端渲染SSR及实现原理 - 掘金 (juejin.cn)
【长文慎入】一文吃透 React SSR 服务端渲染和同构原理 - 掘金 (juejin.cn)
五分钟了解 SPA 与 SSR - 简书 (jianshu.com)
第七种,将png/jpg/gif图片替换为webp格式图片
webp格式的图片比png/jpg有着更优秀的算法。在图片体积上会比jpg/png更小。所以加载的也就更快,耗费的带宽也就越少。占用加载资源的时间也就越短。
webp格式提供有损压缩和无损压缩两种方案。
在线png v/s webp 示例👉WebP 示例 (PNG 转 WebP) (isparta.github.io)
你也可以使用Squoosh来进行在线压缩。然后自己比对下大小(免费的)。
也不用过于担心兼容性问题,已经有95%左右的浏览器支持webp格式。而对于不支持的浏览器,我们也可以采用兼容写法(不过ie完全不支持)。
(体积就是带宽,带宽就是金钱)
参考文章
webp格式图片相对于png/jpb格式图片优点 - 掘金 (juejin.cn)
WebP 相对于 PNG、JPG 有什么优势? - 知乎 (zhihu.com)
第八种,合并请求
为什么要合并请求?
为了减少请求时间,为了减小服务器压力。ajax请求并不是没有成本的。每次请求都需要进行TCP的三次握手和四次挥手,解析报文等一系列的过程,这些过程都需要时间去执行。并且,浏览器在同一域名下的请求并发数有限制,同一域名下同一个请求只能并发一个,不同类型请求(比如GET/POST)并发个数基本在4-6个之间。假设当前浏览器的并发请求有6个。那么第7个请求就需要等前6个请求中任意一个完成以后才可以从任务队列中被拉出去执行。所以,合并请求可以在一定程度上减少资源响应时间,给用户带来更好的使用体验。
浏览器请求并发参考图👇
测验方法👇(打开网络 => 调整为慢速 => 刷新网页)
合并请求的基本方案
- 使用精灵图(合并静态图片资源请求)
- 合理合并get请求,在适当的情况下,我们可以将一些可以合并的get请求合并为一个
第九种,启用事件委托(事件代理)
事件代理 === 事件委托 (一个东西,叫法不同,以下简称事件委托)
什么是事件委托?利用事件冒泡机制将原本应该绑定在子元素上的事件全部交由父元素来完成的行为被称为事件委托。
事件委托可以减少内存消耗和DOM操作
举个例子🌰(出于可读性方面考虑,以下案例用原生js编写)
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
...
</ul>
/*题目:假设我们需要打印li中的值*/
//方案一 (逐个绑定事件,然后点击打印自身内容)
const doms = document.getElementsByTagName("li");
for (let i = 0; i < doms.length; ++i) {
doms[i].addEventListener("click", (e) => { console.log(e.target.innerText) });
}
//方案二(事件绑定在父元素(ul)上,但由子元素(ul)触发)
const dom = document.getElementsByTagName("ul")[0];
dom.addEventListener("click", (e) => { console.log(e.target.innerText) });
注意方案一和方案二的区别,方案一li自身触发事件,自身执行事件。方案二,li自身触发事件,但全由父元素去执行。方案一需要绑定N个(取决于li的数量)事件。方案二不论由多少个li只需要绑定一个事件。绑定的事件越多,在内存中的占比就越大。
事件委托适用场景:列表数据和瀑布流数据等需要大量绑定相同功能的函数的场景。
参考文章:
超详细!!关于事件冒泡/事件捕获,事件代理(事件委托)这里有你所需要知道的一切。 - 掘金 (juejin.cn)
DOM 事件与事件委托(事件代理) - 知乎 (zhihu.com)
第十种,尽量使用CSS完成动画效果
一些简单的,需要手动绘制的动画,在CSS可以完成的情况下,尽量避免使用JS完成动画。
使用CSS完成动画的好处是:
- 不占用主线程(js是需要占用的)
- 可以利用硬件加速
- 在不可见时动画不会持续执行
当然,如果项目本身存在动画库,建议使用动画库。如果动画复杂,无法使用css完成(比如需要绑定函数),那么建议用JS完成动画。
第十一种,适当使用memo --- React篇
react的渲染机制,只要父组件的state改变,那么不论子组件是否发生改变。子组件都会重新渲染。这就导致了一个问题,如果父组件包含N个子组件并且父组件渲染频繁的情况下。N个子组件会一直重新渲染。
这个问题可以由React中的memo解决。memo是一个缓存组件。使用memo包含子组件,那么在memo的依赖不改变的情况下。子组件不会随着父组件的渲染而重新渲染。当然,这不意味着我们可以滥用memo,memo本身是存在成本的。没有必要给每一个组件都加上memo。比如父组件如果不存在重新渲染的情况,那么子组件就没有必要用memo包含。
memo有两个坑点请注意
- 如果传入函数,在函数没有被useCallback包涵的情况下,父组件渲染后,每次默认传入的函数都是一个新函数,意味着memo包含的子组件每次都会重新渲染。
2.memo校验的是props中传入变量的栈内存地址,像传入对象这种复杂数据类型,数据是存储在堆内存的情况下,不new一个新对象,老对象里面的数据改变了memo也监测不到。无法及时更新。
参考文章如下↓
react性能优化篇之memo的作用和memo的细节讲解 - 掘金 (juejin.cn)
React 顶层 API – React (reactjs.org)--官网
第十二种,使用懒加载
对于一些不必要立即显示的节点,我们可以采用懒加载技术。在需要使用到的时候,再去加载该文件(组件),以减少不必要的内存占用和页面负载。
常见使用场景:瀑布流,下拉列表。子组件渲染时机(vue/react)。
第十三种,使用骨架屏
对于用户来说,很多用户会在网页端长时间的白屏状态下失去耐心,然后离开页面。所以在数据查询速度慢,或者资源体积大,数量多无法第一时间返回等浏览器无法快速接受并将数据渲染到视图上的情况下,除了可以采用代码压缩,启动缓存等方案外,我们还可以采用骨架屏的方式来给客户挽回一点体验。
相关文章
前端骨架屏方案小结 - 掘金 (juejin.cn)
第十四种,将moment.js换成day.js
好处:
- day.js的体积比moment.js小。moment.js有70多kb,但是day.js只有2kb。像微信小程序这种对代码包大小有要求的情况下,day.js会是比moment.js更好的选择。很多官方的框架和库都已经将moment.js换成了day.js。
- moment已经好几年没更新了。但是day.js仍在持续更新中。
从moment.js迁移到day.js学习成本并不高,因为day.js是moment.js的微缩版,api相似度极高。
dayjs中文网:Day.js中文网 (fenxianglu.cn)
momentjs中文网:Moment.js 中文网 (momentjs.cn)
最后
祝各位在寒冬中都能找到满意的工作。
版权归原作者 一只小松鼠_ 所有, 如有侵权,请联系我们删除。