vue2vue2中的性能优化(持续更新中)

Posted 初映CY的前说

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue2vue2中的性能优化(持续更新中)相关的知识,希望对你有一定的参考价值。

⭐ v-for 遍历避免同时使用 v-if
⭐ v-for 中的key绑定唯一的值
⭐ v-show与v-if对性能的影响
⭐ 妙用计算属性
⭐ 使用防抖与节流控制发送频率
⭐ 路由守卫处理请求避免重复发送请求
⭐ 使用第三方UI库的引入方式

【前言】

该系列是博主在使用vue2开发项目中常用上的一些小Tips,学的开心!

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

在 Vue2 中当v-for与v-if同时用的时,v-for的优先级比v-if高。这样就会造成一种现象:不管v-if的判断条件,vue都会把这个v-for循环走完再来判断。那么我们应该怎么把数据进行渲染过滤之后再显示提高性能呢?使用计算属性。
请看下面这段代码:

<template>
  <div>
    <ul>
      <li v-for="item in filterList" :key="item.id">
         item.user 
      </li>
    </ul>
  </div>
</template>
<script>
export default 
  name: 'father-page',
  data () 
    return 
      list: [
         isActive: true, user: '初映', id: 1 ,
         isActive: false, user: '测试1', id: 2 ,
         isActive: false, user: '测试2', id: 3 ,
         isActive: true, user: '测试3', id: 4 ,
         isActive: false, user: '测试4', id: 5 
      ]
    
  ,
  computed: 
    filterList () 
      return this.list.filter(item => 
        return item.isActive
      )
    
  

</script>

<style>
div 
  color: teal;
  margin-top: 50px;

</style>

代码分析:我们将筛选与渲染到页面这两个步骤抽离出来了。

  • 我们通过计算属性对我们的原数组进行一个过滤筛选的操作(将isActive:true)的项进行返回。
  • 遍历的对象不再是我们原本的list而是我们过滤之后的filterList。这样就避免了v-if与v-show没有一起使用进一步的提升了我们的性能啦!

⭐ v-for 中的key绑定唯一的值

key是必须加上的,不加上的化ESlint也提示缺少配置项。key值是唯一标识,目的是为了更加高效的更新虚拟DOM。可当我们不在v-for中的key添加唯一标识,会发生什么错误呢?
页面渲染正常,但是浏览器会报错。提示key值重复错误:
这是v-for中key需要写唯一的值,其他地方也需要吗?尽量使用唯一标识,因为key其目的是确保正确的更新。没有key默认是就地复用的策略,可能会导致复用了的页面的结构不会更新导致渲染发现数据缺失。

⭐ v-show与v-if对性能的影响

这两者的作用是一样的,控制我们组件的是否渲染在页面上。使用场景进一步的细分会进一步提高我们性能。
v–if的执行底层是对一个组件的创建与销毁。这样就会导致一个问题,如果是需要多次触发渲染的组件就会导致我们对组件频繁的渲染与销毁。那么处理频繁触发的组件显示与否,我们更推荐使用v-show。
v-show的执行底层是类似于控制组件的css样式的显示与否,我们这样做就避免的在频繁的切换过程中我们对于组件的创建又销毁了。
综上所述:v-if更推荐用于页面不是经常需要切换的场景(不需要切换的时候就不需要创建新的组件,我们的性能会更高)。v-show适合需要频繁切换的场景中。

⭐ 妙用计算属性

计算属性是一种处理数据用来解决代码的冗余的方式,当处理不需要常变化的值推荐使用这个。计算属性就有缓存机制,这种机制可以提高我们的性能。当我们数据有变化的时候才执行,没变化时无论怎么调用都只是执行第一次的结果。只有当计算属性中的值发生变化时,这个函数才会重新执行一次。
计算属性写法:

computed:
变量名()
return this.//需要处理的数据


上述只是基本的概念,更多相关内容,大家可移步:计算属性与侦听器的用法区别

⭐ 使用防抖与节流控制发送频率

防抖:在规定时间内触发最后一次。节流在规定时间内触发规定次数。这两者根据使用场景来能很好的提升用户的使用感受。比如做发送验证码功能的时候,我们可以利用节流的特性让用户避免发送多个验证码,当用户点击‘发送验证码’之后我们将该按钮设置为禁止再次触发。

⭐ 路由守卫处理请求避免重复发送请求

路由守卫是作用于我们与页面之间的跳转。业务中常有一个这样的逻辑判断:
我存在token了我才可去白名单中的页面,当其他页面也需要其他信息时候就需要再次发送请求获取了。逻辑当然也是没毛病,但是我们可以优化一下我们请求。

  const token = store.state.user.token
  if (token) 
    if (to.path === '/login') 
      // console.log('已经存在token了,不会跳登录页,现在去首页')
      next('/')
     else 
      // console.log('token存在不是去登录页,给与放行')
      if (!store.state.user.userInfo.userId) 
        // 前面加上await与async 是便于我们获取信息之后再去跳转以达到:页面跳转前我们的数据就收到了,否则可能会因为网速等原因报错
        const menus = await store.dispatch('user/getUserInfo')
      

代码释意:当我们存在token与我们在其他页面有共用的信息。我们就可以写一个判断,这样就可以第一次页面加载的时候发送请求,之后token存在的时候就不需要发送请求再去获取我们需要的信息了,从而达到降低发送ajax请求来提高我们的性能。

⭐ 使用第三方UI库的引入方式

在使用第三方UI库的根据业务来看判断按需引入还是全部引入。这个就比较好理解了,全部引入资源加载量多,项目运行的速度会变慢一点。按需引入就能够按需所取(强烈推荐)
关于全家桶中UI库的使用方式,大家可移步:ElementUI与Vant2的引入使用方法

至此本文结束,愿你有所收获!
期待大家的关注与支持! 你的肯定是我更新的最大动力

C++/C 使用过程中的经验总结荟萃(持续更新)

同一个算法,不同的人写出来,效率上可能会差1000倍。写程序的同时,要注意性能。

玄学

所谓的玄学,主要发生在有些情况下你写的程序压根就是错的,只不过当前的编译环境下因为某些不知名的原因没识别出错误,换个环境就报错了。不同的编译环境对于错误的包容方面是不一样的,有的环境下,这种错误不识别,有的环境下,那种错误不识别。总之,你就是写错了的,编译器没认出来。所以说,规范编程很重要,不要感觉,编译和运行不报错就可以乱来。

命名空间

使用命名空间可以有效地防止命名冲突

左值非const

  • 在C++语言中,对于一个由类名加俩冒号再加成员名构成的东西(学名叫“quilified-id”),比如:A::x,只有当x是A类的静态成员的时候,A::x才能表示一个左值。

  • 对于函数类型到函数指针类型的默认转换,只有当函数类型是左值的时候才行。所有对于非静态的成员函数,就不存在这种从函数类型到函数指针类型的默认转换,于是编译器也就不知道这个

结构体 set 和 map

set中放入结构体或者自定义对象时,结构体的写法,要重载小于号,注意是 bool operator<(const A & )const 的形式(map一样要重载)。关于比较函数,return false 就是交换,直到任意两个输入,走一遍都能return true。具体怎么比的,就很迷。

STL 容器选择

  • C++ 的 STL 容器主要分为序列式和关联式。所谓的序列式宏观上理解就是小鬼们按一定的顺序排排坐。所谓关联式类似于数据库里面,有一个 key,有一个值这样的。序列式的容器包含 array、vector、list、deque,关联式的容器有 map、set 和 multi 以及 unordered 两个层面的变种,除此之外,还有在学数据结构时,耳熟能详的栈(stack)、队列(queue)等。
  • 很多人写算法,想到哪个就用哪个,这是很不对的,在选择容器的时候,我们要考虑程序的性能。那么应该如何选择合适的容器呢?如果你想表达的数据,stack 或者 queue 的特征已经非常明显了,直接用他俩;如果对数据后续要有大量的查找,就用关联式容器,其中又以无序的查找最快,但是它无序;如果有大量的添加和删除操作(特别是在中间),对次序有要求,选择 list,而尽可能地避免 vector 和 array;如果对元素的次序要求比较高,而且有随机访问(所谓的随机访问,是说有按下标比如说v[3],v[5]这样访问的),且没有元素在中间的插入或者删除,且没有极其大量的查找,可以选择 vector 和 array…知道或者大概知道长度的,尽量用array。

关于-O3的过度优化

O3的优化很神奇,他会自动判断你哪些代码是无效,比如说某个循环不运行可能对结果没影响,她就会给你忽略过去。

开O3优化会让你产生一些错觉。找热点和调试程序的时候尽量不要开-O3优化。主要原因是数据区用满了,-O3优化下,不会报错,不同的全局变量之间,相互污染了。

内存上的性能优化

内存尽量整一块,不要东找找,西找找。任务集中,相同的任务放在一起做。

简单地说,如果你哪个变量在全局变量开太大,导致 bss 越界了,就可以用 malloc 动态开辟内存,虽然速度慢点,但是没有问题。

全局的未初始化变量存在于.bss段中,具体体现为一个占位符。全局的已初始化变量存于.data段中。

系统允许你的数据是 1G ,那么这1G = 已初始化数据 + bss。

关于 set 和 map

  • map和unordermap更适合建数据库,以供查询。

  • 用bool向量来替代删除,有时候可以避免内存的开辟和变量的迁移。用什么数据结构内存占用等方面本质相同的。差距在于操作时是否有内存的开辟,以及数据的拷贝。

  • 别一开始就定义很完整的结构体,先做主要工作,后面需要什么再补上。

  • 结构体set得自己重载比较规则。

  • set map虽然查找很快,但是建立非常慢,适合只会增加出现,很少会没的东西。

  • 容器的insert其实一种拷贝,而不是引用。

  • 能不用指针的,尽量别用。

  • map和set是插入的时候才排序。如果过程中有值的改变,是不会进行重排序的。

const_cast的妙用

学会用 const_cast 处理一些报错。

关于函数传递数组与指针

  • 同一个变量的多次声明,都指向一个地址,比如,你定义了 int a多次,其实都是指向一个地址。

  • 一个经验总结就是:c++能不用指针就不用指针,尽量用引用。指针我们是用不好的,尤其是多重指针。

  • 存数组,不能把临时数组的指针当成这个属于存下来,否则你下次指针指向的内容改变了,数组也就变了。同一个临时变量的多次重新声明和定义,地址总是不变的,并不会开辟新的内存。

匿名表达式

C++ 11 新特性,用多了是不是很酷。
[=] (int x, int y) -> bool return x%10 < y%10;
我们可以看到,匿名表达式无非就是一般的函数把函数名隐藏,然后把返回类型用->放到后面。加了一个中括号,表示捕获列表,即从外部传入函数体内的一些变量,包括引用和非引用方式的传入。= 就是非引用传入,& 就是引用传入,值可以被修改。也可以单独地传入某些个外部变量。

Debug 模式和 Release 模式

一般来说,release 模式跑起来更快。但是,有时写内存访问冲突问题,release 模式下不报错,但是再在debug 模式下会报错,也有的时候有问题也都不报错。删除了一个数之后,for 循环的迭代器是否会继续往后走,是未知的,有时候会,有时候不会。C++ 的内存这个东西,是个玄学,保证代码正确即可,不必深究。如:

int main()

	vector<int> a =  7,6,5,4,3,2,1 ;
	for (auto& it : a)
	
		cout << "it:" << it << endl;
		if (it == 6)
		
			for (auto ite = a.begin(); ite != a.end(); ite++)
			
				if (*ite == it)
					a.erase(ite);
			
			cout <<"after del:" << it << endl;

			
	
	return 0;

全局变量

vector<int> a(10); 等价于 vector<int> a(10,0);存放于存于.data段中。

Ubuntu 修改C头文件(链接库、环境变量)目录

最直接的就是在/etc/profile添加,然后关机重启,部分情况下 source 一下也可以。LD_LIBRARY_PATH 是动态链接库,LIBRARY_PATH 是静态链接库。目录只会在给定的路径下查找,而不会递归地查找子文件夹。冒号拼接,最后不加斜杆。看清楚需要添加的路径是哪个,不要少加了几层,也不要多加了几层。

export C_INCLUDE_PATH=$C_INCLUDE_PATH:/usr/include/libxml2:/home/test/Desktop/boost_1_75_0
export CPLUS_INCLUDE_PATH=$CPLUS_INCLUDE_PATH:/usr/include/libxml2:/home/test/Desktop/boost_1_75_0
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/test/Desktop/boost_1_75_0/stage/lib  
export LIBRARY_PATH=$LIBRARY_PATH:/home/test/Desktop/boost_1_75_0/stage/lib  

类中vector的初始化

在类XXX中创建一个10个元素的vector A, 初始值为0:

class XXX
public:
	vector<int> A(10, 0);
......	

报错expected identifier before numeric constant,意为:数字常量前应有标识符。原因: 编译器认为, 你正在定义一个成员函数,函数名为A ,返回值类型为 vector ,函数有两个输入参数(10, 0), 但是参数的给定方式有错误。修改:

class XXX
public:
	vector<int> A = vector<int> (10, 0);
......	

vector在使用迭代器遍历过程中,不能使用erase (),否则出错

关于类型重命令

vector带大小不代表一种类型,所以typedef std::vector<double>(3) v3type;是不合法的。C++ 11的标准下可以使用using identifier = type;替代typedef。

explicit关键字

C++提供了关键字 explicit,使用 explicit 关键字,便不允许构造函数的隐式转换调用。如下,t3 的构造是会报错的,因为explicit,规定了 Test2 的构造只能使用默认的构造。

#include <iostream>
using namespace std;
class Test1

public :
	Test1(int num):n(num)
private:
	int n;
;
class Test2

public :
	explicit Test2(int num):n(num)
private:
	int n;
;
 
int main()

	Test1 t1 = 12;
	Test2 t2(13);
	Test2 t3 = 14;
		
	return 0;

static_cast

基本类型之间的转换,但不能用于基本类型指针之间的类型转换(void指针和基本类型指针之间可以)。

 double d=0;
 int i=static_cast<int>(d);

关于 const 成员函数重载

如果类实例为const,就调用的const函数,否则调用非const。直接看例子。

using namespace std;  
   
class Test  
  
protected:  
    int x;  
public:  
    Test (int i):x(i)    
    void fun() const  
      
        cout << "fun() const called " << endl;  
      
    void fun()  
      
        cout << "fun() called " << endl;  
      
;  
   
int main()  
  
    Test t1 (10);  
    const Test t2 (20);  
    t1.fun();  //此处调用fun()  
    t2.fun();  //此处调用 fun() const
    return 0;  

函数模板类型推断不能通过左值

在推断函数模板的类型时,类型必须显示地出现在函数的调用表达式里面,否则会出错。如:

template <Uint _Size>
inline SVectorCL<_Size> std_basis(Uint i)

    SVectorCL<_Size> ret(0.);
    if (i>0) ret[i-1]= 1.;
    return ret;


//正确调用
SVectorCL<5> myVector9 = std_basis<5>(2);
//错误调用
SVectorCL<5> myVector9 = std_basis(2);

关于枚举类型

所谓的枚举,本质就是限定一个变量只能取一些离散的整数值,为了增加可读性,往往会找一些字母串来表示作为这些整数值得代号。枚举变量的值只能取枚举常量表中所列的值,就是整型数的一个子集。基本的写法如下:

enum color_set1 RED, BLUE, WHITE, BLACK; // 定义枚举类型color_set1
enum week Sun, Mon, Tue, Wed, Thu, Fri, Sat; // 定义枚举类型week

关于传入参数的默认值

一般情况下,在函数调用时形参从实参那里取得值,因此实参的个数应与形参相同。有时多次调用同一函数时用同样的实参,C++ 提供简单的处理办法,给形参一个默认值,这样形参就不必一定要从实参取值了。这种方法比较灵活,可以简化编程,提高运行效率。

关于模板

处理模板函数和类模板时,因为要进行实例化模板函数和类模板,要求编译器在实例化模板时必须在上下文中可以查看到其定义实体。因为编译器不会会预先知道 typename 实参是什么呢。因此模板的实例化与定义体必须放到同一翻译单元中。也就是 h 文件和 cpp 分离式的写法,模板函数实现最好写在 h 文件中。某个类的成员函数,如果是模板函数,也要和声明定义写在一块。

头文件定义

定义头文件都要养成一个好习惯,

#ifndef DROPS_FEMP3_H
#define DROPS_FEMP3_H
xxx
#endif

防止重复调用带来的重定义。

for_each

模板函数原型:

template<typename InputIterator, typename Function>
Function for_each(InputIterator beg, InputIterator end, Function f) 
  while(beg != end) 
    f(*beg++);

for_each 的返回值是其第三个参数。
用法示例:

# include<iostream>
# include<algorithm>
# include<vector> 
 
using namespace std;
 
void Print(int val)

	cout << val << " ";

 
/*
基本数据类型 
遍历算法
*/
void test1()

	vector<int> v;
	
	v.push_back(1);
	v.push_back(5);
	v.push_back(4);
	v.push_back(2);
	v.push_back(6);
	
	for_each(v.begin(),v.end(),Print);
  
 
/*
自定义数据类型
for_each遍历 
*/ 
class Person
	public:
		Person(int a,int b):index(a),age(b)
	public:
		int age;
		int index;
;
 
void Print2(Person &p)

	cout << "index:" << p.index << " " << "age:" << p.age << endl;

 
void test2()

	vector<Person> vp;
	Person p1(1,4),p2(2,5),p3(3,6);
	vp.push_back(p1);
	vp.push_back(p2);
	vp.push_back(p3);
	
	for_each(vp.begin(),vp.end(),Print2); 

 
int main()

	//test1();
	test2();
 
 return 0;

关于 mem_fun

mem_fun、mem_fun_ref 的作用就是将一个"成员函数指针"包装成一个仿函数。当容器中存放的是对象实体的时候用 mem_fun_ref,当容器中存放的是对象的指针的时候用 mem_fun 。

class  ClxECS

public :
     int  DoSomething() 
     
         //  这里以输出一句话来代替具体的操作
        cout  <<   " Output from method DoSomething! "   <<  endl; 
         return   0 ; 
    ;
;

vector < ClxECS *>  vECS;

for ( int  i  =   0 ; i  <   13 ; i ++ )

    ClxECS  * pECS  =   new  ClxECS;
    vECS.push_back(pECS);


int  DoSomething(ClxECS  * pECS)

     return  pECS -> DoSomething();


for_each(vECS.begin(), vECS.end(),  & DoSomething);//方式1
for_each(vECS.begin(), vECS.end(), mem_fun( & ClxECS::DoSomething));//方式2

上述方式 1 访问需要单独定义一个全局的仿函数。要直接调用 ClxECS 中对应的成员函数,才有了方式 2。
DoSomething 函数是无参的,而对于有一个参数的函数,可以使用std::bind 辅助函数。

std::bind2nd

std::bind1st 和 std::bind2nd 函数用于将一个二元算子转换成一元算子。bind 的意思是“绑定”,1st 代表 first,2nd 代表 second。 例子如下:

#include <iostream>
#include <functional>
#include <algorithm>
#include <vector>
using namespace std;
 
int main()

    vector<int> coll =  1,2,3,5,15,16,18 ;
 
    // 查找元素值大于10的元素的个数
    // 也就是使得10 < elem成立的元素个数 
    int res = count_if(coll.begin(), coll.end(), bind1st(less<int>(), 10));
    cout << res << endl;
 
    // 查找元素值小于10的元素的个数
    // 也就是使得elem < 10成立的元素个数 
    res = count_if(coll.begin(), coll.end(), bind2nd(less<int>(), 10));
    cout << res << endl;
 
    return 0;
 

std::transform

std::transform在指定的范围内应用于给定的操作,并将结果存储在指定的另一个范围内。要使用std::transform函数需要包含<algorithm>头文件。

template <class InputIterator, class OutputIterator, class UnaryOperation>
  OutputIterator transform (InputIterator first1, InputIterator last1,
                            OutputIterator result, UnaryOperation op);
	
template <class InputIterator1, class InputIterator2,
          class OutputIterator, class BinaryOperation>
  OutputIterator transform (InputIterator1 first1, InputIterator1 last1,
                            InputIterator2 first2, OutputIterator result,
                            BinaryOperation binary_op);

类成员变量的初始化

类成员变量的初始化,一般都放在构造函数里面,类内不能直接初始化。如下写法是错误的。

class Solution 
    vector<int> r(10);
;

正确的写法是通过构造函数初始化。

class Solution
Type() : vec(10)   // 要这么初始化
vector<int> vec;
;

因为类只是定义的类型,还没有实例化,也就是没有定义类的对象(变量),没法存储,你可以在初始化列表里进行初始化。

自定义默认代码

#include <iostream>
using namespace std;
int main()


    return 0;

valarry

valarray 是一个类模板,面向数值计算,优势是重载了各种数学函数,方便数学计算。
valarray::resize 将 valarray 中的元素数更改为指定数量,如果第二个传入参数省略,默认赋值为 0。

unique_ptr

动态内存的使用很容易出问题,因为确保在正确的时间释放内存是极其困难的。有时会忘记释放内存,在这种情况下会产生内存泄露和非法指针的引用。而智能指针,当该对象被销毁时,会在其析构函数中删除关联的原始指针。就不用再专门地删除对象了。

为了更容易(同时也更安全)地使用动态内存,C++11标准库提供了两种智能指针(smart pointer)类型来管理动态对象。智能指针的行为类似常规指针,重要的区别是它负责自动释放所指的对象。C++11标准库提供的这两种智能指针的区别在于管理底层指针的方式:shared_ptr 允许多个指针指向同一个对象,unique_ptr 则"独占"所指向的对象。

它的用法就是把指针形如 Task* 改成 std::unique_ptr<Task>

accumulate 自定义数据类型的处理

四个形参:头两个形参指定要累加的元素范围,第三个形参则是累加的初值,第四个回调函数来实现自定义数据的处理。

#include <vector>
#include <string>
using namespace std;
 
struct Grade

	string name;
	int grade;
;
 
int main()

	Grade subject[3] = 
		 "English", 80 ,
		 "Biology", 70 ,
		 "History", 90 
	;
 
	int sum = accumulate(subject, subject + 3, 0, [](int a, Grade b)return a + b.grade; );
	cout << sum << endl;
 
	system("pause");
	return 0;

dynamic_cast

dynamic_cast 运算符的主要用途:将基类的指针或引用安全地转换成派生类的指针或引用,并用派生类的指针或引用调用非虚函数。如果是基类指针或引用调用的是虚函数无需转换就能在运行时调用派生类的虚函数。

前提条件:当我们将 dynamic_cast 用于某种类型的指针或引用时,只有该类型至少含有虚函数时(最简单是基类析构函数为虚函数),才能进行这种转换。否则,编译器会报错。

numeric_limits

提供了类型的一些数值极限,如:

 cout<<"numeric_limits<int>::min()= "<<numeric_limits<int>::min()<<endl; //int的最小值  
 cout<<"numeric_limits<int>::max()= "<<numeric_limits<int>::max()<<endl;  //int的最大值  

虚函数

一言以蔽之,基类类型的指针指向子类,该指针调用的同名函数是子类的。

mutable 关键字

在C++中,mutable 是为了突破 const 的限制而设置的。可以用来修饰一个类的成员变量。被 mutable 修饰的变量,将永远处于可变的状态,即使是 const 函数中也可以改变这个变量的值。

大括号

在C/C++中大括号指明了变量的作用域,在大括号内声明的局部变量其作用域自变量声明始,到大括号之后终结。我们应该善用它,使我们的程序更加清晰明白。C++ 的大括号不是无意义,它圈定了大括号里面变量的生存周期。

typename 关键字

有时候,在使用模板参数,容易引起歧义的时候,我们会在前面加个 typename 关键字,说明后面是个类类型,而不是别的东西。

const

c++ 函数前面和后面 使用const 的作用:

  • 前面使用const 表示返回值为const
  • 后面加 const表示函数不可以修改class的成员

以上是关于vue2vue2中的性能优化(持续更新中)的主要内容,如果未能解决你的问题,请参考以下文章

C++/C 使用过程中的经验总结荟萃(持续更新)

C++/C 使用过程中的经验总结荟萃(持续更新)

C++/C 使用过程中的经验总结荟萃(持续更新)

前端性能优化小结(持续更新)

前端性能优化小结(持续更新)

Java 中的5个代码性能提升技巧,最高提升近10倍