11-Vue常见优化手段

前言:

永远不要过早优化

具体项目,具体分析,脱离场景看优化没有什么意义

使用key

对于通过循环生成的列表,应给每个列表项一个稳定且唯一的key,这有利于在列表变动时,尽量少的删除,新增,改动元素,因为diff算法就是通过key值来进行比较的

index作为key值是唯一的,但不够稳定,比如插入某元素,会导致后面的所有元素重新渲染,而我们想要的是只渲染新加的元素

比如有一个列表,我们需要在中间插入一个元素,在不使用 key 或者使用 index 作为 key 会发生什么变化呢?先看个图


如图的 li1 和 li2 不会重新渲染,这个没有争议的。而 li3、li4、li5 都会重新渲染
因为在不使用 key 或者列表的 index 作为 key 的时候,每个元素对应的位置关系都是 index,上图中的结果直接导致我们插入的元素到后面的全部元素,对应的位置关系都发生了变更,所以在 patch 过程中会将它们全都执行更新操作,再重新渲染。
这可不是我们想要的,我们希望的是渲染添加的那一个元素,其他四个元素不做任何变更,也就不要重新渲染
而在使用唯一 key 的情况下,每个元素对应的位置关系就是 key,来看一下使用唯一 key 值的情况下


这样如图中的 li3 和 li4 就不会重新渲染,因为元素内容没发生改变,对应的位置关系也没有发生改变。
这也是为什么 v-for 必须要写 key,而且不建议开发中使用数组的 index 作为 key 的原因

对于展示数据使用冻结对象Object.freeze()

冻结的对象不会被响应化

 当观察的对象是嵌套对象的时候,就需要递归

如果对象很多,嵌套结构很深,遍历的时间就会变长

思考:

所有的属性都需要响应式吗?

在原始数据中,有些不需要去响应式的,叫做展示数据,比如商品列表,商品价格图片这些都不能操作的

数据响应式:数据变化后,会自动重新运行依赖该数据的函数(重要)

  1. 被监控的函数

    render、computed回调、watch、watchEffect

  2. 函数运行期间用到了响应式数据(响应式数据一定是个对象)

  3. 响应式数据变化会导致函数重新运行

Vue会判断对象是否被冻结Object.isFrozen(),如果为true就不遍历

let obj = {
	a: 1,
	b: 3
}
/* 冻结对象 */
Object.freeze(obj);
obj.c = 2;
console.log(obj); // {a: 1, b: 3}

/* 判断对象是否被冻结 */
console.log(Object.isFrozen(obj));  // true

判断对象是否被冻结Object.isFrozen()

Object.isFrozen(obj);

冻结前:
在这里插入图片描述
冻结后:
在这里插入图片描述
相比于冻结前,每个属性都少了get和set方法,渲染速度更快。

使用函数式组件(无状态,无实例)

有些时候,我们一个组件只是需要接受一些prop数据,而不会改变什么东西,这个时候可以用函数式组件 ,函数式组件在渲染的时候能减少一定的脚本运行时间,内存占用也少一些

渲染函数 & JSX — Vue.js

我们可以将组件标记为 functional,它只是一个接受一些 prop 的函数,这意味它无状态 (没有响应式数据),也无实例 (没有 this 上下文)。一个函数式组件就像这样:

Vue.component('my-component', {
  functional: true,
  // Props 是可选的
  props: {
    // ...
  },
  // 为了弥补缺少的实例
  // 提供第二个参数作为上下文
  render: function (createElement, context) {
    // ...
  }
})

 

 

 

普通组件,js内存增加了30M

函数式组件,js内存增加了20M

 

 Vue不会为函数式组件创建任何一个实例

查看VNode虚拟节点,可以看到normal组件看不到函数组件

 

 使用计算属性

如果模板中某个数据会使用多次,并且该数据是通过计算机得到的,使用计算属性可以缓存,(缺陷不能传参)

非实时绑定的表单项(v-model.lazy

vue设计思想是关注的是数据而不是界面,代码的可维护性和可阅读性也很重要,js执行线程和浏览器渲染线程是互斥的,所以运行动画时执行jS线程动画会卡顿

当使用 v-model 绑定一个表单项时,当用户改变表单项的状态时,也会随之改变数据,从而导致vue发生重渲染(rerender),这会带来性能的开销

特别是当改变表单项时,页面有动画在执行,由于js线程和浏览器线程是互斥的,最终会导致动画出现卡顿。

可以通过使用v-model.lazy(失去焦点时),或者不使用v-model,但是这样可能导致在某一时间段内数据和表单项的值是不一样的。

当使用  绑定一个表单项时,当用户改变表单项的状态时,也会随之改变数据,从而导致 vue 发生重渲染(rerender),这会带来一些性能的开销。

特别是当用户改变表单项时,页面有一些动画正在进行中,由于JS执行线程和浏览器渲染线程是互斥的,最终会导致动画出现卡顿。

我们可以通过 v-model.lazy (失去焦点时) 或不使用 v-model 的方式解决该问题,但是注意,这样可能会导致在某一个时间段内数据和表单项是不一致的

v-model 监听 @input 事件

v-model.lazy  监听 @change 事件

保持对象引用稳定 (组件要不要重新渲染)

在绝大多数情况下, vue 触发 rerender 的时机是其依赖的数据发生变化

若数据没有发生变化,哪怕给数据重新赋值了, vue 也是不会做出任何处理的

function hasChanged (x, y) {
	if (x === y) {
		return x === 0 && 1 / x !== 1 / y;
	} else {
		return x === x || y === y;
	}
}

 NaN === NaN  false
NaN !== NaN  true

+0 === -0  true
1/+0   Infinity
1/-0  -Infinity

如果需要,只要能保证组件的依赖数据不发生变化,组件就不会重新渲染

对于原始数据类型,保持其值不变即可

对于对象类型,保持其引用不变即可

从另一方面来说,由于可以通过保持属性引用稳定来避免子组件的重新渲染,那么我们应该细分组件来尽量避免多余的渲染

比如,做用户评论,提交最新的一条评论后,更新评论列表,有两种方法

1、添加后,重新全部从服务器拿(缺点:rerender触发频繁)

2、添加后,把新加的对象直接加到现有数据的最前面(缺点:数据不是实时的)

还是那个思想,我们只想要渲染新加的那个对象,其他的不渲染

在添加评论列表的时候,添加一个数据之后,一般我们会再调用一次获取数据的接口,获取完数据,拿到了json对象,然而这个对象和原来的是两个不同的数据,然后就会重新渲染,但是很多数据都是相同的,没有必要重新渲染。所以我们可以在新增加一个数据之后,直接在原来的数据中把这个新增的数据加上,不用再调用获取所有数据的借口了,问题就是如果别人也在期间加了数据,那末页面显示的可能不实时(别人新增的展示不出来),但是我们也没有规定必须要实时展示数据,因为只要一刷新就好了。

通过这个也能看出,组件细分的好处,可以避免多余的渲染。

 

 

使用v-show替代v-if

对于频繁切换显示状态的元素,使用 v-show 可以保证虚拟DOM树的稳定,避免频繁新增和删除元素,特别是对于那些内容包含大量DOM元素的节点,这一点极其重要

使用延迟装载(defer)

具体实现多种多样,有组件分批加载,数据分批加载

首页白屏时间主要受到两个因素的影响:

  • 打包体积过大

    巨型包需要消耗大量的传输时间,导致JS传输完成前页面只有一个<div>,没有可显示的内容

  • 需要立即渲染的内容太多

JS传输完成后,浏览器开始执行JS构造页面

但可能一开始要渲染的组件太多,不仅JS执行的时间很长,而且执行完成后浏览器要渲染的元素过多,从而导致页面白屏

一个可行的方法是延迟装载组件,让组件按照指定的先后顺序依次一个一个渲染出来

本质是利用 requestAnimationFrame 事件分批渲染内容

 

 

 

 react fiber

使用keep-alive

正常情况下,页面切换的时候会进行销毁,不想它被销毁的话就需要用keep-alive包裹着组件

比如列表页和详情页的切换,可以使用 keep-alive 进行缓存组件,防止同样的数据重复请求

用于缓存内部组件实例,里面有include和exclude属性,max设置最大缓存数,超过后,自动删除最久没用的。

受到keep-alive影响,其内部的组件都具有两个生命周期,activated和deactivated ,分别再组件激活和失活时触发,第一次activated是在mounted之后。

一般用在需要多个页面频繁操作的场景(导航条)

  <keep-alive>
       <router-view />
    </keep-alive>

长列表优化

例如某乎页面数据量为1万条数据,在分批加载数据前提下怎样优化才能滑动不卡顿。

因为每次 DOM 修改,浏览器往往需要重新计算元素布局,再重新渲染。也就是所谓的重排(reflow)和重绘(repaint)。尤其是在页面包含大量元素和复杂布局的情况下,性能会受到影响。

一个常见的场景是大数据量的列表渲染。通常表现为可无限滚动的无序列表或者表格,当数据很多时,页面会出现明显的滚动卡顿

方案1:
做分页处理,首页默认展示第一页数据,滚动加载,这也是一种方案,如果列表做不了分页,那该如何处理。

方案2:

当有大量列表需要展示的时候,不需要一次性将所有列表渲染出来,只需要渲染可视区域及可视区域以下的一部分列表,并为这些已经渲染的列表设置绝对定位,然后通过计算滚动位置来不停更新开始区域的列表即可,不需要渲染所有的数据列表


虚拟列表的实现原理:只渲染可视区的 dom 节点,其余不可见的数据卷起来,只会渲染可视区域的 dom 节点,提高渲染性能及流畅性,优点是支持海量数据的渲染;当然也会有缺点:滚动效果相对略差(海量数据与滚动效果的取舍问题就看自己的需求喽
使用vue-virtual-scroller插件

cnpm install -D vue-virtual-scroller
main.js 引入这个插件:
import "vue-virtual-scroller/dist/vue-virtual-scroller.css";
import Vue from "vue";
import VueVirtualScroller from "vue-virtual-scroller";

Vue.use(VueVirtualScroller);


方案3:
滚动节流减少服务器压力。

方案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:

添加css属性 ,这样滚动时滚动条是抖动的,添加了属性后的滚动条变长,并且拖动不流畅,但是做到了按需加载

  • content-visibility: auto:如果该元素不在屏幕上,并且与用户无关,则不会渲染其后代元素。
  • 在一些需要被频繁切换显示、隐藏状态的元素上,使用 content-visibility: hidden,用户代理无需重头开始渲染它和它的子元素,能有效的提升切换时的渲染性能;
  • content-visibility: auto 的作用更加类似于虚拟列表,使用它能极大的提升长列表、长文本页面的渲染性能;
  • 合理使用 contain-intrinsic-size 预估设置了content-visibility: auto 元素的高宽,可以有效的避免滚动条在滚动过程中的抖动;
  • content-visibility: auto 无法直接替代 LazyLoad,设置了 content-visibility: auto 的元素在可视区外只是未被渲染,但是其中的静态资源仍旧会在页面初始化的时候被全部加载;
  • 即便存在设置了 content-visibility: auto 的未被渲染的元素,但是它并不会影响全局的搜索功能。
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Document</title>
	<style>
		p {
			content-visibility: auto;
			line-height: 40px;
		}
		.container {
			overflow: auto;
		}
	</style>
</head>

<body>
	<div class="container"></div>

	<script>
		let container = document.querySelector('.container');
		let innerHTML = '';
		for(let i = 0; i < 1000; i++) {
			innerHTML += `<p>第${i}行,我是长列表</p>`
		}
		container.innerHTML = innerHTML;
	</script>
</body>
</html>

{ content-visibility: auto; contain-intrinsic-size: 1px 5000px;}_勒布朗-前端的博客-CSDN博客

打包体积优化(webpack)

分为打包速度优化和打包体积优化

猜你喜欢

转载自blog.csdn.net/iaz999/article/details/131427389
今日推荐