【工程化】收集平时积累的一些性能优化措施,以及提示一些性能检测方案,还有分享我面试是怎么去回答这类问题的

前言

平时在项目中如果遇到了一些性能优化的问题,在通过自己的专研解决了之后,再忙也要挤时间记录下,且要把对应的项目场景记录下来。

因为这种经验是你真正硬实力的体现,并且面试都喜欢问这个问题。

以下代码举例混合了vue和react,且不同层面的优化方法存在一些交集,归类到哪里其实不重要。


js代码层面的优化

节流和去抖

可以看我的文章: 节流和去抖(注意看里面的举例),建议自己也能手写一个简单的节流和去抖函数。

在vue和react能做的事

举例vue(react做法差不多)

  • 主动销毁全局实例(例如定时器、图表实例化等)
  • 监听数组变量时,利用重新赋值的方式代替深度监听deep
  • 计算属性的使用
  • 模板for循环的key值绑定,还有v-for与v-if不同时使用(每次多个if判断)
  • v-show和v-if的区别
  • keepalive缓存等等。

减少前端与交互无用代码

例如一些稍微复杂的数据运算、或者状态判断,能不能交给后端处理,再返回给前端。减少除视图操作层面外的代码量。

例如一个按钮的显示,前端没必要拿之前某个接口的数据,结合另外一个接口的数据,然后相互判断去决定一个按钮如何显示。

DOM操作可以优化

主要是通过 createDocumentFragment,一次性对dom做操作。可参考:DOM的操作优化

动画优化

以html5和css3的特性去代替过去老旧的用js实现的动画方案。

使用requestAnimationFrame

这个是可以代替setTimeOut的,例如一个这样的场景,一次性处理并渲染1w条数据,并且不想有loading效果。但JS是单线程,dom的渲染共同使用一个线程,这么多数据页面会加载的很卡。有小伙伴就会说了,分步骤加载,用setTimeOut实现每次处理加载500条,直到完成为止。

但这样有个问题,在60hz的情况下,要做到不卡顿,必须每次循环时间设定在1000/60ms左右。而定时器不只是到设定时间后执行的,它还要等待上一轮的微任务完成后才会执行,所以这个过程一定是大于17ms,还是会卡顿。

这个时候就需要我们的requestAnimationFrame,它是以系统时间为主,每次以1000/60ms的时间让JS线程单独处理下事情,这样他能保证每1000/60ms页面得到一帧:

let index = 0 // 当前执行次数
let target = 50000 // 总目标,执行多少次
function dealBigDateFn() {
    requestAnimationFrame(() => {
        let sliceTarget = index + 500 > target ? target : index + 500 // 阶段性目标,每次处理500条
        for (; index < sliceTarget + 1; index++) {
            console.log('dom不绘制就执行', index);
        }
        if (index < target + 1) {
            dealBigDateFn()
        }
    })
}
dealBigDateFn()

做动画也是一样的,例如一个进度条,你想让他每一帧都增加固定宽度,从0到100%。这时候如果你用setTimeOut,有可能是第一帧18ms、第二帧38ms、第三帧29ms…,这样看下来一秒都不够60帧了,肉眼就能感觉到卡顿。这时候用这个api代替,完美搞定:

let index = 0 // 当前执行次数
let target = 100 // 总目标,执行多少次
function newTimeOut() {
    let timer = requestAnimationFrame(function fn(){
        if (index < target + 1) {
            console.log('宽度增加', index);
            index++
            timer = requestAnimationFrame(fn)
        } else {
            cancelAnimationFrame(timer)
        }
    })
}
newTimeOut()

使用DOMContentLoaded

DOMContentLoaded开始执行JS而不是load

window.addEventListener('load', function(){}) // 页面的全部资源加载完毕才会执行,包括图片、视频等

document.addEventListener('DOMContentLoaded', function(){}) // dom渲染完毕即可,此时图片、视频还可能没加载完。

例子:dom在渲染的时候,img图片的加载并不会阻塞。

const img1 = document.getElementById('img1')
img1.onload = function () {
    console.log('img loaded') // 2
}

window.addEventListener('load', function () {
    console.log('window loaded') // 3
})

document.addEventListener('DOMContentLoaded', function () {
    console.log('dom content loaded') // 1
})

这玩意估计也用不上,基本都用框架的生命周期代替了


网络层面的优化

接口缓存

为了减少网络传输量和提升接口速度,可以考虑使用强缓存和协商缓存

如果后端不支持,是否可以前端对一些数据比较固定的接口做缓存。哎不过个人目前没实际应用过强缓存和协商缓存,好像没碰到什么场景非用不可。如果要用的话,估计就是一些比较大的静态文件吧,例如图片,视频之类的。

CDN加速

如果使用了CDN,使用按区域分配ip的对应CDN。

架构上的优化

例如大厂有时候喜欢加个BFF层等等

图片优化

例如webp图片方案

webp格式的图片能够在不影响画质的前提下,减少几乎50%的大小。

  • 做法一:是需要后端把图片转成webp保存到数据库中,当前端的请求头accept中包含支持webp的信息时,通过Nginx的配置请求到对应的webp文件。

  • 做法二:做法一的基础上,后端不需要存webp,只需要借助第三方的在线格式转换即可(资金投入大)

开启gizp压缩

能够让请求的资源容量直接少2/3,默认情况下框架打包时都开启了,然后服务端的Nginx里也要配置下。

听说又来了个br压缩,能够比gizp再进一步压缩,这更新速度汗颜啊


页面加载层面的优化

SSR服务器渲染

主要分为:

  • 静态html:在打包项目的时候,会去请求接口数据,拿到数据后编译好一个静态的html文件,浏览器请求对应的地址后,直接把文件给浏览器返回
  • 动态html:(猜测)在打包的时候,不会请求接口数据,要等浏览器访问对应页面地址后,才会去根据情况请求对应的数据,然后编译好一个html文件返回给浏览器

实现技术例如用Next.js搭建的react项目,但是吧!一般的公司也不会要求某个页面要加载的很快。用到这技术的基本都是比较大型,对首页性能要求很高的c端网页,一般开发者都是接触不到的。这玩意估计大厂喜欢弄。

个人觉得搞这个还不如通过webpack去做资源加载优化。

资源按需加载

例如

  • 文件资源按需加载:组件和库的按需引入
  • 数据按需加载:页面展示数据太多,可以做分页或者下滑加载更多。图片过多也可以用图片懒加载。

标签/静态资源的渲染优先级问题

有些前置知识点要知道。

为什么要统一把css文件放在html结构中的head中,因为如果放在dom前面和后面,会重复dom树和css树合成render树的这一个动作。

为什么要把script放在dom后,因为如果放在dom中间或者前面,页面没渲染或者渲染到一半就会卡住去执行js代码,页面渲染中间就会有显示不完全的情况出现,所以应该放在dom后面。

还有script标签的引入时机:

<script src='xxx'></script>
<script src='xxx' async></script>
<script src='xxx' defer></script>
  • 普通的写法是,当html解析时,碰到js文件,会先下载然后解析js文件,解析完成后再继续html解析(这样页面渲染容易阻塞)
  • async是:当html解析时,碰到js文件,html继续解析同时下载js文件,下完后先解析js文件,解析完成后再继续html解析(html没解析完但js下载完了,也会阻塞)
  • defer是:当html解析时,碰到js文件,html继续解析同时下载js文件,html完全解析完后,最后再执行js文件(怎么都不会阻塞)

当然还有一个颗粒度更新的控制,就是importance属性指定的资源的加载优先级。很多标签也能用这个属性。属性值也就auto,high,low这三个权重,用了可以在F12网络请求里看到Priority。

刻意减少重排的过程

可以看看这篇文章:刻意减少重排的过程

虚拟列表的应用

可以使用这俩个css属性:

.box{
	content-visibility: auto; // hidden表示,不渲染标签内部的东西,auto表示,标签不在预览窗口时,不渲染内容,在就渲染
	contain-intrinsic-size: 500px; // 这个属性的作用是,让其知道每个box标签的高度好让浏览器显示出正确的滚动条
}

网上那些写的太复杂了,真的要用到优先考虑引入的组件库支不支持


打包层面上的优化

具体可以看【webpack】7-针对不同场景的性能优化方案,了解一些配置手段。

vite的个人暂时还只是一个处在刚接触的阶段,以后有空了再补充。

减少入口文件的大小

这里我给打个样,举例react,首先可以安装一个库source-map-explorer,这个库可以分析出当前项目资源的结构(vue好像脚手架就已经集成了),按照链接当前页操作会给你生成一个资源结构图。

通过这个库生成的资源结构图,我们可以分析每个资源的打包大小情况,例如我们的main.js里面有合进很多第三方的库,照成这个文件单个体积量大。

可以通过启动本地打包好的项目(可以通过serve指令帮忙),用F12检查看看是否和结构图反映的一样:

npm install serve -g
serve -s build

一般情况下F12看到的资源大小会比资源结构图上看到的容量少2/3,是因为浏览器自带gzip容量压缩功能。

感觉如果一个资源文件大小大于200kb(gizp),可以考虑把这个文件进行拆分了。

可以先考虑把一些复杂的路由组件进行按需引入分开打包,然后再去分析下资源结构图,看看对应的组件文件是不是真的分出去了。

如果按需引入的组件和main.js有共同用到的第三方库,还是会编译在main.js里。所以有时候效果不一定很好,除非这个按需引入的组件资源解耦性很强。

这个时候,可以采用【抽离公共代码和第三方代码】的方案。这样main.js又会大大减少。

抽离加上组件的按需加载,再配合浏览器的缓存策略,用户下次访问我们的网站时只会去拉取更新过的资源文件,提升了用户体验。


回答套路举例

具体要怎么答?记住两点:

  • 按照大的分类去描述
  • 在描述的过程中一定要结合你的项目场景,如果实在忘记了,就不要傻傻的去回想,立马把面试官针对的项目引导到这种通用性能方案的处理

问:请问你在你的项目中用到了那些性能优化措施呢?

答:我在我的项目中使用到了很多关于性能优化的手段。例如在js代码层面上,巴拉巴拉(结合项目场景)…,在网络层面上,…


其实有些东西还想讲下:

  1. 总的来说性能优化方向?
    多考虑空间换时间,减少网络加载耗时。

  2. 性能优化是必要的吗?
    个人觉得一些细小的“优化点”,真的无伤大雅,例如css少使用后代选择器这种,现在的电脑性能已经不像过去那么弱了,一个页面上再怎么复杂,多几个后代选择器的影响也是微乎其微的。


如何检测性能

JS执行时间

推荐使用

let startD = performance.now()
// ...
let endD = performance.now()
console.log(endD - startD)

这种方式比console.time计算的准确

渲染情况

JS记录

全局中有个叫做window.performance的对象,里面有个time属性,记录了页面一开始加载的各种数据记录。具体指标对应可以百度下。

浏览器自带

可以用浏览器开发者工具自带的performance和lighthouse去检测。

第三方网站

可以参考一个B站视频:项目上线后,如何分析网站的性能指标?

不过里面推荐的网站需要梯子。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值