day-093-ninety-three-20230616-前端性能优化方案-辅助知识
前端性能优化方案
CRP思想
- 当代前端性能优化的主要方案:CRP思想「按照关键的渲染路径进行优化」;再结合一些性能监测工具,可以分析出性能消耗较大的地方,有针对性的进行优化;但最后整体来讲,我们主要还是围绕以下几方面进行优化:
-
第一部分:加快打包速度和降低打包后文件的体积
- 基于DllPlugin插件,把一些固定不变的依赖进行缓存,后期打包的时候就不会再打包这些文件了!
- 可基于新一代打包工具 vite/turbopack 代替 webpack!
- 基于 SplitChunksPlugin 插件对代码进行分割,让页面首次加载的JS文件变的更小!
- 在webpack打包编译的时候,尤其是生产环境下,我们应该取消ESLint的检测、取消sourceMap文件的生成,以此来加快项目的打包速度!
- 应该尽可能忽略对 node_modules 的处理,加快打包的速度
- …
-
第二部分:提高页面第一次渲染的速度
-
减少HTTP请求的次数和大小
- 使用 CSS Sprite(雪碧图)技术,把N多小图合并为一张大图
- 对于非关键性资源(例如:图片、第三方插件、音视频等等),在页面首次渲染的时候,进行懒加载
- 尽可能的进行封装,减少页面冗余代码,降低打包后文件的体积(还需要对代码进行压缩)
- 第三方UI组件库需要按需导入
- 服务器端开启GZIP压缩,加快HTTP的传输效率
- 把CSS合并为一个,JS也合并为一个,以此来减少HTTP请求的次数!
-
前端骨架屏技术
- 这种方式,可以让页面第一次更快的渲染出来,减少白屏等待时间!
- SSR服务器渲染的骨架屏「主流模式」:vue + nuxt.js + node.js
- 在服务器抗压能力较强的情况下,页面的首屏信息是服务器渲染的;第一次加载页面,只要向服务器发送请求,请求回来的HTML页面中,首屏的内容已经是在服务器渲染好的,客户端直接呈现即可!
其余屏的数据,依然借用客户端骨架屏方案,由客户端动态获取数据进行绑定!- 客户端骨架屏:在页面第一次渲染的时候,把需要呈现内容的部分,用一些骨架(可以是灰色背景,也可以是Loading效果)先占位,第一次渲染完,再向服务器发送请求,把获取的真实数据,渲染到指定的区域!
-
开启静态资源文件的CDN部署
-
基于 Connection:keep-alive 保持TCP通道的长链接,防止每一次请求都要进行三握四挥,加快资源的获取
-
减少对Cookie的使用,因为每一次向服务器发送请求,都会把本地的cookie自动传递给服务器
-
资源要分服务器进行部署,在增加了DNS解析次数的基础上,使用DNS预解析,来加快域名解析速度!
-
针对于图片方面的处理
- 使用字体图标或者矢量图,来代替位图
- 网站中使用位图,优先使用 webp 格式,其次是 jpeg/jpg 、png 、gif 这些格式
- 基于webpack,对小于一定范围的图片进行BASE64处理,减少HTTP请求的次数,加快图片的渲染!「但是不要乱用,因为使用了BASE64,会增加html/css等文件的体积;而且增加了代码维护的成本(我们一般都需要基于webpack进行base64编译)」
- …
-
对于数据请求,可以采用分批次(例如:分页、触底加载)和异步加载(或数据的延迟加载)
-
建议减少对
@import
的使用,因为其会阻塞GUI的渲染!对于移动端页面,如果样式代码较少,可以使用内嵌式! -
尽量把
<link>
放在页面的头部,让CSS资源的获取和GUI的渲染同时进行,加快页面的渲染;尽量把<script>
放在页面的尾部,因为其会阻碍GUI的渲染,当然也可以基于设置async/defer,让<script>
变为异步的! -
在SPA单页面应用开发的时候,一定要做 路由懒加载!「目的:把各组件的代码分开打包,减少主JS的体积,加快页面第一次渲染的速度」
-
网站可以采用 HTTP/2.0 协议!
-
减少HTML的层级嵌套,加快DOM-Tree的构建!
-
在Vue/React的项目开发中,我们可以把一些文件,以单独的JS形式导入,例如:
<script src="../vue.min.js"></script> <script src="../main.deqwewe.js"></script>
- 这样做,可以让不会改变的框架代码,一直使用的是缓存机制!!
-
-
第三部分:提高项目运行时的性能
- 使用 防抖、节流 处理:防抖是只触发一次,节流是降低触发频率!
- 合理使用闭包,并且手动清除不用的内存,以减少内存的开销「注意内存泄漏问题」!
- 减少直接操作DOM,降低页面的重排(回流)和重绘!
- 基于事件委托实现事件的绑定,可以提高产品运行的性能「提高40%~60%」
- 对静态资源文件开启强缓存和协商缓存,对ajax请求的数据,基于本地存储进行临时性的缓存!
- 在编写CSS样式的时候,要精简选择器(尤其是选择器前缀),因为CSS选择器是从右到左渲染的
- 尽可能的使用CSS3动画(尤其是配合 transform 修改元素的样式),减少对JS动画(比如定时器、requestAnimationFrame)的使用,坚决不用flash动画,因为CSS动画性能更好!
- 减少对CSS表达式的使用「例如:expression、calc」,这些东西都比较消耗性能
- 项目开发中,一定要规避死循环/死递归,而且减少for/in循环的使用;基于循环处理指定的逻辑时,要考虑“时间复杂度 O(n)”,减少循环的嵌套!
- 在项目开发中(尤其是Vue/React框架开发中),在组件销毁的时候,记得清除手动设置的 定时器、监听器 等,释放无用的内存!
- 减少页面中 iframe 的使用!「
<iframe>
相当于视图框架,可以允许在父页面中,嵌入子页面;window.length 记录的就是页面中iframe的数量」 - 超长列表的虚拟滚动处理「以后讲」
- 对于需要实时通信的需求(例如:微信聊天、股市走向图、直播类、支付中的某些需求…), 尽可能基于 scoket.io 代替 定时器的长轮询!
- 在Vue/React中,循环创建的元素一定要设置唯一的key值,而且最好不要用索引,因为这个key值是用来优化DOM-DIFF的!对于计算属性和监听器(computed/watch、useMemo/useCallback),也需要合理的使用,因为他们本身就消耗一些性能!
- JS中不要使用 with/eval 等语句,因为这些语句非常非常消耗性能!
- 对于较大的图片上传,我们可以使用切片上传和断点续传
- 减少对Table标签的使用!
- vue中合理选择v-show和v-if,v-show适用于频繁切换节点,v-if适合只更改一次的情况!
- 在React框架中,基于 shouldComponentUpdate 进行组件更新优化!{例如:继承PureComponent来对新老属性/状态进行浅比较}
- 在Vue开发中,把从服务器获取的数据基于 Object.freeze() 进行冻结,让其不成为响应式数据!
- 在SPA单页面项目中,对于某些组件采用
<keep-alive>
实现组件的缓存
-
辅助知识
事件基础知识
-
什么是事件?
- 事件是浏览器赋予元素的默认行为,即便什么都不处理,元素该具备的事件都有!
- 浏览器标准事件
-
什么是事件绑定?
-
给元素的事件行为绑定方法,当事件行为触发,会执行对应的方法!
-
DOM0事件绑定:
box.onclick = function(){ ... }
- 原理:给元素对象的 onxxx 事件私有属性赋值
- 只能给元素的此事件行为绑定一个方法
- 必须拥有此事件私有属性才可以,某些标准事件是没有对应的事件私有属性的「例如:DOMContentLoaded」
- 原理:给元素对象的 onxxx 事件私有属性赋值
-
DOM2事件绑定:
box.addEventListener('click',function(){ ... },布尔/对象)
- 参数
- 最后参数是布尔类型值,是控制触发的阶段 true:捕获 false:冒泡
- 最后参数是对象,则是设置相关的配置项
- capture 控制触发阶段
- once 只能触发一次,触发完毕后,会自动基于 removeEventListener 移除事件绑定
- passive 设置为true之后,则禁止阻止默认行为
- 原理:利用浏览器内置的事件池机制,完成事件绑定及触发管理的
- 只要是浏览器支持的标准事件,都可以用 addEventListener 做事件绑定「比DOM0的功能强大」
- 可以给同一个元素的同一个事件类型,绑定多个不同方法,当事件触发的时候,所有绑定的方法会依次被触发执行
- 参数
-
-
什么是事件对象?
-
所谓事件对象,就是一个对象,只不过记录了本次触发的相关信息;当事件触发,绑定的方法执行,会把分析好的事件对象,作为实参传递给每个函数!!
box.addEventListener('click',function(ev){ // ev:获取的事件对象信息 })
-
常用的事件对象:MouseEvent(PointerEvent/TouchEvent)、KeyboardEvent、Event…
-
事件对象中常用的信息:
- altKey: false
- ctrlKey: false
- shiftKey: false
- clientX/clientY 操作点距离可视窗口的坐标
- pageX/pageY 操作点距离BODY的坐标
- srcElement/target 事件源
- type 事件类型
- which/keyCode 键盘的按键码
- …
- preventDefault 阻止默认行为
- stopPropagation/stopImmediatePropagation 阻止事件传播
- composedPath 获取传播的路径「事件源 -> … -> Window」
- …
-
-
事件的传播机制
- 对于支持事件传播的事件行为来讲,当事件行为触发的时候,会经历三个阶段:
- CAPTURING_PHASE: 1 捕获阶段「从window开始查找,一直找到事件源,其目的是为冒泡阶段分析好传播路径」
- AT_TARGET: 2 目标阶段「把事件源的相关事件行为触发」
- BUBBLING_PHASE: 3 冒泡阶段「并让其祖先元素的相关事件行为也被触发,而且是从事件源 -> Window」
- 有一些事件行为是不支持“事件传播”的,例如:
- mouseenter/mouseleave
- load
- …
- 对于支持事件传播的事件行为来讲,当事件行为触发的时候,会经历三个阶段:
-
事件委托「事件代理」
- 事件委托就是利用事件的传播机制,来实现的一种项目解决方案
- 例如:一个容器中有很多后代元素,其中大部分后代元素,在点击的时候,都会处理一些事情「有些元素点击做相同的事情,有些点击做不同的事情」
- 传统方案:想操作哪些元素,就把这些元素全部都获取到,然后逐一做事件绑定
- 这种方案不仅操作起来复杂,并且会开辟很多堆内存,性能也不是很好
- 新方案:利用事件的传播机制,不逐一获取元素和单独绑定事件了,而是只给外层容器做一个事件绑定,这样不管点击其后代中的哪一个元素,当前容器的点击事件也会触发,把绑定的方法执行;在方法中,我们只需要判断事件源是谁,从而处理不同的事情即可! ===> 事件委托
- 这种新方案的性能比传统方案提高40%以上!
- 例如:我们容器中的元素不是写死的,而是动态绑定(动态创建)的,而且会持续动态创建;我们需要在点击每个元素的时候,做一些事情!
- 传统方案:每一次动态创建完,都需要获取最新的元素,给这些元素单独做事件绑定!
- 事件委托:只需要给容器的点击事件绑定方法,不论其内部元素是写死的,还是动态绑定的,只要点击了,都说明元素已经存在了,基于事件传播机制,我们只需要在外层容器中判断事件源,做不同的事情即可!
事件委托可以给动态绑定的元素做事件绑定!!
- 例如:我们容器中的元素不是写死的,而是动态绑定(动态创建)的,而且会持续动态创建;我们需要在点击每个元素的时候,做一些事情!
- 这种新方案的性能比传统方案提高40%以上!
- 传统方案:想操作哪些元素,就把这些元素全部都获取到,然后逐一做事件绑定
- 真实项目中还有很多需求,必须基于事件委托来完成!所以:以后再遇到事件绑定的需求,首先想是否有必要基于事件委托处理,如果确定有必要,则直接基于事件委托处理!
-
mouseenter 与 mouseover的区别:是否会事件冒泡。
-
拖拽操作:
- mousedown/mousemove/mouseup 「注意:鼠标焦点丢失问题」
- dragstart/drag/dragend 拖拽事件
- dragenter/dragleave/drop 拖拽中让元素进入/离开目标位置&放在目标位置上
- 使用这套事件模型,需要给元素设置draggable属性
事件对象的特殊性
- 每一个事件操作,都有且只有一个事件对象。事件传播过程中的事件对象都是同一个。
- 比如说点击一个元素,就会有一个事件对象产生。但在捕获阶段和冒泡阶段所触发的事件中,途中所经历的各个DOM元素的事件属性上绑定的各个事件中,所接收到的事件对象都是同一个内存地址的对象。在该对象上绑定的属性,也会在各个函数中共享。
base64
-
src为url的图片的渲染步骤。
- 经过对url解析之后发起网络请求,获取url对应的资源。
- 对资源进行base64编码。
- 把base64编码进行渲染成页面。
-
src为base64编码的图片的渲染步骤。
- 直接把base64编码渲染成页面。
http协议版本
-
HTTP1.0 VS HTTP1.1 VS HTTP2.0
-
HTTP1.0和HTTP1.1的一些区别
- 缓存处理,HTTP1.0中主要使用 Last-Modified,Expires 来做为缓存判断的标准,HTTP1.1则引入了更多的缓存控制策略:ETag,Cache-Control…
- 带宽优化及网络连接的使用,HTTP1.1支持断点续传,即返回码是206(Partial Content)
- 错误通知的管理,在HTTP1.1中新增了24个错误状态响应码,如409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除…
- Host头处理,在HTTP1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个IP地址。HTTP1.1的请求消息和响应消息都应支持Host头域,且请求消息中如果没有Host头域会报告一个错误(400 Bad Request)
- 长连接,HTTP1.1中默认开启Connection: keep-alive,一定程度上弥补了HTTP1.0每次请求都要创建连接的缺点
-
HTTP2.0和HTTP1.X相比的新特性
- 新的二进制格式
Binary Format
,HTTP1.x的解析是基于文本,基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认0和1的组合,基于这种考虑HTTP2.0的协议解析
决定采用二进制格式
,实现方便
且健壮
header压缩
,HTTP1.x的header
带有大量信息,而且每次都要重复发送,HTTP2.0使用encoder来减少需要传输的header大小,通讯双方各自cache一份header的fields表
,既避免了重复header的传输,又减小了需要传输的大小- 服务端推送
server push
,例如我的网页有一个style.css的请求,在客户端收到style.css数据的同时,服务端会将style.js的文件推送给客户端,当客户端再次尝试获取sytle.js时就可以直接从缓存中获取到,不用再发请求了 - 多路复用
MultiPlexing
: 多个请求可同时在一个连接上并行执行,某个请求任务耗时严重,不会影响到其它连接的正常执行;
- 新的二进制格式