性能分析—前端性能优化方向探究

目录

一、概述

二、前端优化必读

1、初探浏览器渲染

2、回流和重绘

3、探究CSS和JS阻塞问题

4、外部JS脚本的引用方式

5、script和link标签对DOM解析和渲染的影响

6、Canvas和SVG渲染

7、浏览器渲染产生图层

8、动画的选择

9、骨架屏(Skeleton)

10、GPU硬件加速

三、JS中的其它性能优化

1、不要覆盖原生JS方法

2、使用事件委托简化代码

3、JS动画

4、节流和防抖

4、图片懒加载

5、使用 webp 格式的图片

四、Vue项目性能优化

1、合理使用v-if和v-show

2、合理使用watch和computed

3、v-for遍历注意事项

4、长列表性能优化

5、事件的销毁

6、图片资源懒加载

7、路由懒加载

8、第三方插件的按需引入

五、服务端渲染


一、概述

        性能优化旨在为用户提供更好的体验,例如页面展示更快、交互响应更快、页面无卡顿等。要做好性能优化,需要理解浏览器加载和渲染的本质,知其所以然,才能更好优化之。本文很多内容,参考掘金、CSDN等社区,如有侵权,可以我联系删除0.0。

二、前端优化必读

1、初探浏览器渲染

渲染过程(以谷歌浏览器为例)

        渲染过程如下所示。

1、解析HTML,生成DOM树,解析CSS,生成CSSOM树
2、将DOM树和CSSOM树结合,生成渲染树(Render Tree)
3、Layout: 有了渲染树,浏览器就知道了网页中有哪些节点,各个节点的CSS定义以及它们的从
           属关系,从而去计算每个节点在屏幕中的位置
4、Painting: 按照算出来的规则,把内容画到屏幕上
5、Display: 将像素发送给GPU,会在GPU将多个合成层合并为同一个层,并展示在页面中
   CSS3硬件加速的原理就是新建合成层,组合这些图层便是性能优化中的关键

        注意:渲染引擎会尽可能早的将内容呈现到屏幕上,并不会等所有HTML都解析完成后再去构建和布局渲染树,它是解析完一部分内容,就显示一部分内容。同时,可能还会通过网络下载其余内容。

渲染树(render tree)

        浏览器会解析HTML生成DOM树,每个HTML标签都是DOM树的节点,DOM树包含了HTML中的所有标签,包括处于隐藏状态的内容,也包括JS动态添加的内容。

       浏览器会解析CSS生成CSS树,即样式结构体CSSOM,在解析过程中会去掉浏览器不能识别的样式,例如IE会去掉-moz开头的样式,而火狐会去掉_开头的样式。

        DOM树和CSS树组合会构成渲染树(render tree)。渲染树类似DOM树,但又存在差别。渲染树能识别样式,其上每个节点都有自己的样式。不可见的样式不会进入渲染树,例如一些不会渲染输出的节点,如script、meta、link等,再例如一些通过CSS进行隐藏的节点,如将display设置为none的节点。注意,利用visibility和opacity隐藏的节点,还是会显示在渲染树中。

2、回流和重绘

概念1:重绘(Repaint)

        重绘是指元素自身样式改变引起的浏览器重新渲染目标元素的现象,如改变背景色、文字颜色、边框颜色等。

        重绘不改变目标元素的几何属性,不改变布局,不影响元素本身在文档流中的位置,不影响其他DOM。重绘对浏览器的性能影响很小,优化性能时,一般不予考虑。

概念2:回流(Reflow)

        解释1:当浏览器发现页面某部分发生变化,且影响了布局后,需倒回去重新渲染,这个过程就是回流。回流会从HTML这个根节点开始,递归往下,依次计算所有节点的尺寸和位置。

        解释2:回流是指当渲染树中部分或全部元素的尺寸、结构、或某些属性发生变化时,浏览器根据视口重新计算几何属性。

        回流一定会引起重绘,但重绘不一定引起回流。回流无法避免,且对性能影响很大。目前流行的一些效果,如树状目录的折叠/展开、鼠标滑过、点击等,只要这些操作引起页面上某些元素的占位面积、定位方式、边界等属性变化,都会引起目标元素内部、周围甚至整个页面的回流和重新渲染。

        注意1:display:none的节点不会被挂载到渲染树,而visibility:hidden会被加入。所以若某个节点最开始不显示,设置为display:none是更好的选择。使用display显示和隐藏节点时会触发回流,而使用visibility只会触发重绘,因为位置和尺寸没有变化。

        注意2:浏览器对回流和重绘是有优化策略的,有些情况下,例如修改了元素的样式,浏览器是不会立即执行重绘或回流的,而是把操作放进渲染队列中攒着,达到一定量级或时间后,统一处理。但有些情况,会引起浏览器提前清空渲染队列,因为只有清空队列才能计算出最新的元素尺寸和位置样式信息,已确保特定的属性和方法可以获取到正确的数据,例如resize窗口、改变页面默认字体等,浏览器会马上执行回流。

哪些操作会触发回流

1、浏览器窗口大小发生变化
2、元素尺寸或位置发生变化
3、元素内容发生变化
4、字体大小发生变化
5、添加或删除可见的DOM元素
6、激活CSS伪类,例如:hover
7、查询某些属性或调用某些方法

哪些属性和方法可以引起回流

        如下所有的属性和方法,在读取或执行的同时,会立即触发回流,清空渲染队列。

【1、元素类】

【1.1元素测量】

elem.offsetLeft,elem.offsetTop,elem.offsetWidth,elem.offsetHeight,elem.offsetParent
elem.clientLeft,elem.clientTop,elem.clientWidth,elem.clientHeight
elem.getClientRects(),elem.getBoundingClientRect()

【1.2滚动相关】

elem.scrollBy(),elem.scrollTo()
elem.scrollIntoView(),elem.scrollIntoViewIfNeeded()
elem.scrollWidth,elem.scrollHeight
elem.scrollLeft,elem.scrollTop 除了读取,设置也会触发

【1.3聚焦】

elem.focus() 会触发两次强制布局

【1.4其他】

elem.computedRole,elem.computedName
elem.innerText

【2、getComputedStyle】


概念:getComputedStyle调用通常会导致样式重新计算,且当满足下列条件时,会触发强制布局

【2.1出现下列任意一个媒体查询时】

(1)min-width,min-height,max-width,max-height,width,height
(2)aspect-ratio,min-aspect-ratio,max-aspect-ratio
(3)device-pixel-ratio,resolution,orientation,min-device-pixel-ratio,
   max-device-pixel-ratio

【2.2所获取的属性是下列之一时】

(1)height,width
(2)top,right,bottom,left
(3)margin [-top, -right, -bottom, -left, 或简写形式] 仅在数值是定值时
(4)padding [-top, -right, -bottom, -left, 或简写形式] 仅在数值是定值时
(5)transform, transform-origin, perspective-origin
(6)translate, rotate, scale
(7)grid, grid-template, grid-template-columns, grid-template-rows
(8)perspective-origin

【3、window】

window.scrollX,window.scrollY
window.innerHeight,window.innerWidth
window.getMatchedCSSRules() 仅会导致样式重新计算

【4、表单】

inputElem.focus()
inputElem.select(),textareaElem.select()

【5、鼠标事件】

mouseEvt.layerX, mouseEvt.layerY, mouseEvt.offsetX, mouseEvt.offsetY

【6、document】

doc.scrollingElement 仅会导致样式重新计算

【7、Range】

range.getClientRects(), range.getBoundingClientRect()

优化措施:CSS侧如何避免回流

1、避免使用table布局,该布局通常需要多次计算,花费时间倍数于同等元素
2、尽可能在DOM树的最末端改变class,减少回流的范围
3、避免设置多层内联样式
4、将动画效果应用到absolute或fixed元素上,使之脱离文档流
5、避免使用CSS表达式,例如calc()
6、开启CSS3硬件加速
    1)可以让transform、opacity、filter不会引起回流重绘
    2)尽量使用CSS3的transform来代替top\left等操作
7、为动画元素新建图层,提高动画元素的z-index

优化措施:JS侧如何避免回流

        措施1:避免频繁操作DOM,可以利用文档碎片,批量操作DOM元素。

        在文档碎片上应用所有DOM操作,最后在将之添加到文档中,如此只引起一次回流。

var fragment = document.createDocumentFragment()
fragment.appendChild(目标);
document.body.appendChild(fragment);

        措施2:脱离文档流,操作完后在放进文档流。

elem.style.position='absoulte';
...
elem.style.position='static';

        措施3:可以先为元素设置display:none,使之脱离渲染树,操作结束后再显示出来。

ul.style.display = 'none';
...
ul.style.display = 'block';

        措施4:避免频繁操作样式,最好集中改变。

// 推荐一次性重写style属性,或将样式集中到一个class上
elem.style.cssTxt = 'border-left: 1px; border-right: 2px; padding: 5px;';

        措施5:克隆节点,操作完成后,再替换回去。

const ul = document.getElementById('list');
const clone = ul.cloneNode(true);
...
ul.parentNode.replaceChild(clone, ul);

        措施6:使用变量缓存可能造成回流的属性、方法的值,避免频繁读取。

function init() {
    for (let i = 0; i < paragraphs.length; i++) {
        paragraphs[i].style.width = box.offsetWidth + 'px';
    }
}

// 修改为
const width = box.offsetWidth;
function init() {
    for (let i = 0; i < paragraphs.length; i++) {
        paragraphs[i].style.width = width + 'px';
    }
}

         措施7:能使用CSS3动画的,就不要使用JS动画。

3、探究CSS和JS阻塞问题

知识点1:CSS不会阻塞DOM解析,但会阻塞DOM的渲染

        浏览器的解析渲染过程:解析DOM生成DOM Tree,解析CSS生成CSSOM Tree,两者结合生成渲染树(render tree),最后浏览器根据渲染树,将文档渲染至页面。

        DOM Tree的解析和CSSOM Tree的解析互不影响,两者并行,因此,CSS不会阻塞DOM的解析,但由于渲染树的生成依赖DOM Tree和CSSOM Tree,因此,CSS可以阻塞DOM的渲染。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div>style.css加载完之前,你看不到我</div>
    <div>浏览器解析到link标签时,将立即开始下载CSS样式表,在完成之前不会渲染页面</div>
</body>
</html>

知识点2:JS会阻塞DOM解析

        JS会阻塞DOM的解析,是因为JS中可能对DOM有很大的修改,对DOM树产生影响。如果不阻塞,等浏览器解析完标签生成DOM树后,JS若做出修改,则浏览器又得重新解析,再更新DOM树,性能会比较差。

        JS引擎是独立于渲染引擎存在的,JS代码在文档哪里插入,就在哪里执行。当HTML解析器遇到一个script标签时,浏览器会停止渲染过程,将控制权交给JS引擎。JS引擎对内联JS代码会直接执行,对外部JS文件,需要先下载脚本,再执行。等JS引擎运行完毕,浏览器又会把控制权还给渲染引擎,继续CSSOM和DOM的构建。因此,与其说是JS把CSS和HTML阻塞了,不如说是JS引擎抢走了渲染引擎的控制权。

        实际使用JS时,遵循下面两个原则。

1、CSS资源优于JS资源引入
2、JS尽量少影响DOM的构建

知识点3:CSS会阻塞JS的执行

        JS前面的CSS加载完之前,JS是不会执行的。浏览器遇到没有设置defer或async的script标签时,会触发页面渲染,而页面渲染必须等CSS加载完毕,所以CSS会阻塞JS。

        我们也可以这样想,如果JS中的内容需要获取DOM的样式,CSS没有加载完成就执行JS语句,势必会获取到错误的信息,因此,需要等到JS脚本前面的CSS加载完成,JS才能执行,这才是合理的。

总结1

        CSS的加载不会阻塞DOM树的解析,但会阻塞DOM树的渲染及后续JS的执行,然后JS的执行会阻塞DOM的解析。如下公式可知,即使DOM树解析完成,在CSSOM构建完毕前,浏览器依旧不会渲染任何内容。

DOM树 + CSSOM树 = 渲染树

        存在阻塞的CSS资源时,浏览器会延迟JS脚本的执行和渲染树的构建。CSSOM构建时,JS执行将暂停,直至CSSOM构建完毕。当浏览器遇到一个没有设置defer或async的script标签时,DOM树会停止构建,直至脚本执行完成。

总结2

        JS会阻塞DOM树的解析,但不会阻塞位于JS之前的DOM元素的渲染。

        现代浏览器为了更好的用户体验,渲染引擎将尝试尽快的在屏幕上显示内容。它不会等到所有DOM解析完毕后才布局渲染树,而是当JS阻塞发生时,会将已经构建好的DOM元素渲染到屏幕上,减少白屏的时间。这也是为什么我们一般会将script标签放到body标签的底部,因为这样不会影响前面的页面的渲染。

拓展:样式前面有DOM元素,可能导致页面闪烁

        页面只有当遇到link标签或style标签时才会构建CSSOM,如果这两个标签前面有DOM元素,若加载CSS发生阻塞,样式前的DOM会被浏览器按照默认样式渲染。当CSS加载完成时,则会重新计算样式,重新渲染,此时可能会出现闪烁效果。

        为了避免页面闪烁,通常样式放在文档头部,即head中。

措施1        

        编写HTML时,推荐将CSS放在文档头部,将JS放在文档尾部,此时CSSOM Tree和DOM Tree同时构建,最后CSS树和DOM树合成渲染树。

        引入外部脚本时,样式资源推荐放在文档头部(加速页面的渲染),script脚本推荐放在文档底部,当script位于其它位置时,非特殊情况(如脚本会改变文档的内容),推荐设置defer或者async,因为设置defer或async的script脚本不会阻塞DOM解析。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="demo1.js" async></script>
    <script src="demo2.js" defer></script>
    <script src="demo3.js"></script>
    <link rel="stylesheet" href="style.css">
    <style>/* 内联样式 */</style>
</head>
<body>
    <div></div>
    <script src="demo4.js"></script>
    <script></script>
</body>
</html>

措施2

        CSS会阻塞DOM渲染,会阻塞JS。CSS加载过长,可能导致长时间的白屏,因此合理的优化CSS,提高CSS加载速度,可以大大减少页面渲染所用时间,如下措施可供参考。

1、启用CDN实现静态资源加载速度的优化
2、压缩CSS(可以使用webpack、gulp等)
3、合理使用缓存(主要避免缓存带来的影响)
4、样式文件合并,或干脆写成内联样式(注意,内联样式不能被缓存)

5、使用内联JS和CSS,这样获取到HTML文旦后可以直接开始渲染流程
6、对于大的CSS,可以通过媒体查询属性,将其拆分为多个不同用途的CSS文件,特定场景加载特定CSS
7、若JS中没有操作DOM的代码,可以将其设置async或defer

        CDN翻译为内容分发网络,是一组分布在多个不同地理位置的Web服务器。服务器离用户越远,延迟越高。CDN会根据网络状况,替我们挑选最近的一个具有缓存内容的节点提供资源,从而缩短请求时间。引用外部静态资源时,推荐使用CDN。

拓展

        load事件:页面的HTML、CSS、JS、图片等资源均已加载完之后才会触发。用于监测页面是否完全加载完毕,包括页面及其依赖资源。

        DOMContentLoaded事件:DOM树构建完毕就会触发,无需等待样式表、图像和子框架的完成加载。

4、外部JS脚本的引用方式

defer属性

        示例如下所示,defer是一个布尔属性,规定是否对外部JS脚本的执行进行延迟,直到页面上DOM解析完毕再执行外部脚本。

<script src="demo_defer.js" defer></script>

        每一个defer属性的脚本都会在页面解析完毕后,同时在document的DOMContentLoader事件之前执行。

async属性

       示例如下所示,async是一个布尔属性,规定是否对外部JS脚本采用异步执行方式。设置该属性后,则脚本相对于页面的其余部分异步执行(页面继续进行解析,同时脚本也将被执行)。每一个async属性的脚本都在下载结束后立即执行,同时会在window的load事件之前执行。

<script src="demo_async.js" async></script>

        注意,如果JS前后有依赖性,最好不要用async。

        注意,defer和async仅适用于外部脚本,只有在使用src属性时才有效,对内联脚本无效。

script标签不设置defer或async

        若引用外部脚本时,既不使用async,也不使用defer,浏览器读取后,将立即执行,能够阻塞DOM解析。

5、script和link标签对DOM解析和渲染的影响

script标签会阻塞DOM的解析

        script标签会阻塞DOM的解析,进而阻塞DOM的渲染。如下代码所示,不论是内联还是外部脚本,只要位置在那里,打印结果都将为空

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script>
        const elem = document.querySelector("p")
        console.log(elem) // null
    </script>
    <p>hello word<p>
</body>
</html>

script标签会触发页面的Paint

        script标签或者说JS阻塞渲染,并不是页面不渲染,如果非要等到JS加载完毕在渲染,则等待时间太长。实际上浏览器为了尽快让用户看到页面,在遇到script标签时,会触发一次Paint,借此将script标签之前的DOM元素渲染出来。

        但不是所有script标签都会触发Paint。head标签中的script标签不会触发,毕竟此时body还没解析,根本没有内容来渲染。其次内联的script标签也不会触发Paint。

        因此,建议script标签放在body结束标签之前,这样不会阻塞页面整体的内容的解析和渲染。如果页面中只有内联的script标签,那么放在任何位置,对页面的渲染影响是一样的。

link标签不会阻塞DOM解析,会阻塞DOM渲染

        DOM的解析和CSSOM的解析是一个并行的过程,二者会不影响。两者解析完成之后,会合并生成render tree,之后就是layout和paint阶段,渲染到页面中。

        link标签不会向引用外部脚本的script标签一样会触发页面的Paint。浏览器并行解析生成DOM树和CSS树,当两者都解析完毕,才会生成渲染树,页面才会渲染。所以应尽量减小引入样式文件的大小,提高首屏的展示速度。

link标签会堵塞JS执行

        JS运行时,可能会请求样式信息,如果此时还没有加载和解析样式,JS可能会得到错误的信息,产生很多问题,因此浏览器在link标签加载和解析过程中,会禁止脚本运行,即link标签会阻塞其后script标签的执行。

script标签放在哪里合适      

        script标签一般放在文档头部或尾部。头部是指head标签里面,尾部一般指body结束标签之前。将script标签放在head里面,浏览器发现script标签时,会优先下载脚本内容,之后在往下解析其他DOM节点。

        浏览器不能多个JS并发一起下载,最多只能同时下载两个JS脚本,且浏览器下载JS时,会暂停DOM的解析。因此,将script标签放在头部,会让网页内容呈现滞后,导致用户感觉到卡,所以如非必要,推荐将之放在尾部,以此加速网页的加载。

        将script标签放在尾部也是有缺点的,如此浏览器只能先解析完整个HTML页面,才能去下载JS。而对于一些高度依赖JS的网页,会显得慢了,因此将script放在尾部也不是最优解。

        最优解是一边解析页面,一边下载JS,互不干扰,可以利用async和defer实现。

6、Canvas和SVG渲染

canvas

        除非Canvas本身的位置或者大小发生变化,影响了render tree,才会发生重绘或回流。

        重绘和回流都是相对于render tree上的元素而言的,Canvas本身进行绘制并未对页面其他元素做出更改,则只会引起Canvas画布本身的重绘

SVG

        SVG其实不会触发回流,因为SVG内存在一个坐标系,SVG内的元素在SVG画布上一般都是绝对定位的。仅更新SVG内部的元素,只会相对于SVG,对其所有子元素进行布局的计算。

        SVG内部的DOM元素还是会在DOM树里,也会在Render树里,只不过针对文档流而言,不会有位置的改变,所以不会引起回流。

为什么SVG内部元素多了后,相比Canvas,效率会低很多

        Canvas是即时模式,没有DOM或文档对象模型,在使用Canvas绘制像素时,绘图指令执行后就不管其它了,减少了维护图形内部模型所需的额外内存。

        SVG是保留模式,在使用SVG绘制图像时,绘制的每个对象都会添加到浏览器的内部模型中,使得性能降低。除此之外,SVG依赖于Render树进行渲染,只不过不会触发回流。

7、浏览器渲染产生图层

 概念

        浏览器在渲染一个页面时,会将页面分为很多图层,图层有大有小,每个图层上有一个或多个节点。渲染DOM时,浏览器会做如下工作。

1、获取DOM后分割为多个图层
2、对每个图层的节点计算样式结果 (recalculate style–样式重计算)
3、为每个节点生成图形和位置 (layout–重排,回流)
4、将每个节点绘制填充到图层位图中 (paint–重绘)
5、图层作为纹理上传至GPU
6、组合多个图层到页面上生成最终屏幕图像 (Composite Layers–图层重组)

图层产生的条件

1、拥有具有3D变换的CSS属性:transform:rotate(7deg)、rotateX()、rotateY()、rotateZ()
2、使用加速视频解码的节点
3、<canvas>节点
4、CSS3动画的节点
5、拥有CSS加速属性的元素(will-change)

8、动画的选择

概述

        网页的动画主要有两种实现方式,分别是CSS3动画和JS动画。

CSS3动画

        CSS3动画是关键帧动画,补间动画部分由浏览器完成,便于浏览器进行优化,可以更好的控制动画的执行进程。 CSS3的动画执行在合成线程,专事专干,不阻塞主线程,合成线程的动画也不会触发回流和重绘。

        CSS3动画允许在GPU,专注渲染,更快。

JS动画

        JS动画是逐帧动画,每一帧都是由代码控制,操作不当,极易引发回流。JS的动画执行在主线程,主线程还有其他任务要执行,容易引发阻塞和等待,降低动画执行效率。JS动画运行在CPU,但CPU还有其他任务,易受影响。

        既然JS动画缺点这么多,为什么还要使用JS动画呢。因为JS可以通过编程实现复杂的动画效果,通常使用定时器或requestAnimationFrame实现。

        相比定时器,requestAnimationFrame具有两个优点。其一会把每一帧中的所有DOM操作集中起来,再一次重绘或回流中完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,一般这个频率为每秒60帧。其二隐藏不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这意味着更少的CPU、GPU和内存使用量。

9、骨架屏(Skeleton)

        针对SPA应用进行首屏优化时,需尽可能地减少白屏时间,使首屏内容尽早的展现出来。虽然白屏时间可以通过减少http请求,压缩请求体积,懒加载等各种方法来缩短,但根本上是无法清除的。短暂的白屏会给用户不好的体验,常用的做法可以在内容未加载出来前添加一个loading的动画。初次之外,更好的方式可以使用使用骨架屏,这算是loading的一个进阶版。

        骨架屏是在页面完全渲染之前,用户会看到一个样式简单,绘制了页面大致框架,在感知到页面加载完成时,会替换到骨架屏占位的部分。在现在很多应用中都已经得到试用,如知乎,简书,饿了么等。       

        骨架屏技术主要有两种实现方式。

1、数据没有渲染到页面前,绘制相应图形,为要渲染的DOM元素进行占位
2、前端提前获取DOM节点的形状,等待数据的渲染

10、GPU硬件加速

        GPU硬件加速(hardware acceleration)主要是利用电脑的硬件设备即GPU,分担CPU的一部分任务,降低CPU使用率,从而达到使浏览器运行更加顺畅的目的。

        详情请参考初探GPU硬件加速

三、JS中的其它性能优化

1、不要覆盖原生JS方法

        无论JS代码如何优化,都比不上原生方法,因为原生方法使用低级语言写的(C/C++),并且被编译成机器码,成为了浏览器的一部分。当原生方法可用时,尽量使用它们,特别是数学运算和 DOM 操作。

2、使用事件委托简化代码

<ul>
  <li>苹果</li>
  <li>香蕉</li>
  <li>凤梨</li>
</ul>

<script>
// good
document.querySelector('ul').onclick = (event) => {
  const target = event.target
  if (target.nodeName === 'LI') {
    console.log(target.innerHTML)
  }
}

// bad
document.querySelectorAll('li').forEach((e) => {
  e.onclick = function() {
    console.log(this.innerHTML)
  }
}) 
</script>

3、JS动画

        避免大量使用JS动画,CSS3动画和Canvas动画都比JS动画性能好。

        推荐使用requestAnimationFrame来代替setTime和setInterval,因为requestAnimationFrame可以在正确的时间进行渲染,定时器无法保证渲染时机。

        注意,不要再定时器中绑定事件。

4、节流和防抖

概述

        节流和防抖本质上是一种优化高频率执行代码的一种手段。

        例如浏览器的resize、scroll、keypress、mousemove等事件在触发时,会不断调用绑定在事件上的回调函数,极大的浪费资源,降低前端性能。为了优化体验,需要对这类事件进行调用次数的限制,对此,我们可以采用节流和防抖的方法来减少调用频率。

节流(throttle)

        n秒内只运行一次,若在 n 秒内重复触发,只有一次生效。

const throttle = (fn, delay) => {
  let timer = null
  // 标志是否可以执行函数
  let flag = true
  return function(...args) {
    if (!flag) return
    if (timer) clearTimeout(timer)
    flag = false
    timer = setTimeout(() => {
      fn(...args)
      flag = true
    }, delay)
  }
}

防抖(debounce)

        n秒后在执行该事件,若在 n 秒内被重复触发,则重新计时。

const debounce = (fn, delay) => {
  let timer
  return function(...args) {
    if (timer) {
      clearTimeout(timer)
    }
    timer = setTimeout(() => {
      fn.apply(this, args)
    }, delay)
  }
}

4、图片懒加载

        不管是PC还是移动端,图片一直是流量大头。页面中,先不给图片设置路径,当文档解析好,出现在浏览器可视区后,再去加载真正的图片,这就是延迟加载。对图片很多的网站来说,一次性加载全部图片,会对用户体验造成很大影响,所以需要使用图片延迟加载。

5、使用 webp 格式的图片

        webp是Google团队开发的加快图片加载速度的图片格式,其优势体现在具有更优的图像数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量。同时具备了无损和有损的压缩模式。

        webp既可以有损(代替JPEG),也可以无损(代替PNG),还可以动(代替gif),并且在压缩率上全面超越了这三种常用的格式。

四、Vue项目性能优化

1、合理使用v-if和v-show

v-if

        v-if是真正的条件渲染,它会确保在切换过程中,条件块内的事件监听器和子组件适当的被销毁和重建。v-if也是惰性的,如果在初始渲染时条件为假,则什么都不会做,直到条件第一次变为真时,才会开始渲染条件块。

v-show

        不管初始条件是什么,元素总会被渲染,并且只是简单的基于CSS的display属性进行切换。

结论

        v-if适用于运行时很少改变条件,不需要频繁切换条件的场景。v-show则适用于需要非常频繁切换条件的场景。        

2、合理使用watch和computed

computed

        计算属性,依赖其他属性值,且计算属性的值有缓存。只有计算属性依赖的的属性值发生变化,计算属性才会重新计算。

        当我们需要进行数值计算,且依赖于其它数据时,推荐使用计算属性,因为可以利用缓存特性,避免每次获取值时,都要重新计算。

watch

        更多的起到观察的作用,类似数据的监听回调。每当监听的数据变化时,都会执行回调,进行后续操作。

        当我们需要在数据变化时,执行异步或开销较大的操作时,可以使用watch。watch允许我们执行异步操作、限制执行操作时的频率、设置中间状态等,这些都是计算属性无法做到的。

3、v-for遍历注意事项

v-for遍历必须为item添加key

        列表数据进行遍历渲染时,需要为每一项item设置唯一的key值,方便Vue.js内部机制精准找到数据,有变动时,可以精准实现局部渲染。

v-for遍历避免同时使用v-if     

        v-for的执行级别比v-if高,同时使用会遍历数组的每一项,然后挨个判断v-if,这样会造成不必要的性能开销,影响速度,尤其是只需要渲染很小一部分的时候,推荐使用计算属性。

        推荐。

<ul>
  <li
    v-for="user in activeUsers"
    :key="user.id">
    {
   
   { user.name }}
  </li>
</ul>
computed: {
  activeUsers: function () {
    return this.users.filter(function (user) {
     return user.isActive
    })
  }
}

        不推荐。

<ul>
  <li
    v-for="user in users"
    v-if="user.isActive"
    :key="user.id">
    {
   
   { user.name }}
  </li>
</ul>

4、长列表性能优化

        Vue会通过Object.defineProperty对数据进行劫持,来实现视图响应数据的变化,然而有些时候,组件就是纯粹的数据展示,不会有任何改变,此时就不需要Vue来劫持数据,在大量数据展示的情况下,这能够很明显的减少组件初始化的时间。

        那如何禁止Vue劫持数据呢?可以通过Object.freeze方法来冻结一个对象,一旦被冻结的对象就再也不能被修改了。 

export default {
  data: () => ({
    users: {}
  }),
  async created() {
    const users = await axios.get("/api/users");
    this.users = Object.freeze(users);
  }
};

5、事件的销毁

        Vue组件销毁时,会自动清理它与其它实例的连接,解绑它的全部指令及事件监听器。

        但仅限于组件本身的事件。 如果在JS内使用addEventListener等方式是不会自动销毁的,我们需要在组件销毁时手动移除这些事件的监听,以免造成内存泄露。

created() {
  addEventListener('click', this.click, false)
},
beforeDestroy() {
  removeEventListener('click', this.click, false)
}

6、图片资源懒加载

        对于图片过多的页面,为了加速页面加载速度,所以很多时候我们需要将页面内未出现在可视区域内的图片先不做加载,等到滚动到可视区域后再去加载。这样对于页面加载性能上会有很大的提升,也提高了用户体验。 

7、路由懒加载

        Vue是单页面应用,可能会有很多的路由引入 ,这样使用webpcak打包后的文件很大,当进入首页时,加载的资源过多,页面可能会出现白屏,不利于用户体验。

        如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应的组件,这样就更加高效了,会大大提高首屏显示的速度,但可能其他的页面的速度会降低。

const Foo = () => import(‘./Foo.vue’)
const router = new VueRouter({
  routes: [
    { path: ‘/foo’, component: Foo }
  ]
})

8、第三方插件的按需引入

        项目中经常会需要引入第三方插件,如果直接引入整个插件,会导致项目的体积太大,我们只需要引入需要的组件,以达到减小项目体积的目的。

五、服务端渲染

适用场景

        以下两种情况SSR可以提供很好的场景支持。

        1、需要更好的支持SEO

        优势在于同步。搜索引擎爬虫是不会等待异步请求数据结束后再抓取信息的,如果SEO对应用程序至关重要,但你的页面又是异步请求数据,那SSR可以帮助你很好的解决这个问题。

        2、需更快的到达时间

        优势在于慢网络和运行缓慢的设备场景。传统SPA需完整的 JS 下载完成才可执行,而SSR 服务器渲染标记在服务端渲染 html 后即可显示,用户会更快的看到首屏渲染页面。如果首屏渲染时间转化率对应用程序至关重要,那可以使用 SSR 来优化。

不适用场景

        以下三种场景SSR使用需要慎重。

        1、同构资源的处理

        劣势在于程序需要具有通用性。结合 Vue 的钩子来说,能在 SSR 中调用的生命周期只有 beforeCreate 和 created,这就导致在使用三方 API 时必须保证运行不报错。在三方库的引用时需要特殊处理使其支持服务端和客户端都可运行。

        2、部署构建配置资源的支持

        劣势在于运行环境单一。程序需处于 node.js server 运行环境。

        3、服务器更多的缓存准备

        劣势在于高流量场景需采用缓存策略。应用代码需在双端运行解析,cpu 性能消耗更大,负载均衡和多场景缓存处理比 SPA 做更多准备。

猜你喜欢

转载自blog.csdn.net/weixin_42472040/article/details/125074938