Vue中避免滥用this去读取data中数据


Posted in Vue.js onMarch 02, 2021

前言

在Vue中,data选项是个好东西,把数据往里一丢,在一个Vue组件中任何一个地方都可以通过this来读取data中数据。但是要避免滥用this去读取data中数据,至于在哪里要避免滥用,如果滥用会导致什么后果,本专栏将会一一揭晓。

一、用this读取data中数据的过程

在Vue源码中会把data中数据添加getter函数和setter函数,将其转成响应式的。getter函数代码如下所示:

function reactiveGetter() {
 var value = getter ? getter.call(obj) : val;
 if (Dep.target) {
  dep.depend();
  if (childOb) {
   childOb.dep.depend();
   if (Array.isArray(value)) {
    dependArray(value);
   }
  }
 }
 return value
}

用this读取data中数据时,会触发getter函数,在其中通过 var value = getter ? getter.call(obj) : val; 获取到值后执行 return value,实现读取数据的目的。

这里可以得出一个结论,在Dep.target存在时,使用this去读取data中数据时会去收集依赖。如果滥用this去读取data中数据,会多次重复地收集依赖,从而产生性能问题。

二、Dep.target什么时候存在

Dep.target是由依赖赋值的。依赖又称为Watcher(侦听者)或者订阅者。在Vue中有三种依赖,其中两种是很常见的,就是watch(侦听器)和computed(计算属性)。还有一种隐藏的依赖———渲染Watcher,在模板首次渲染的过程中创建的。

Dep.target是在依赖创建时被赋值,依赖是用构造函数Watcher创建。

function Watcher(vm, expOrFn, cb, options, isRenderWatcher) {
 //...
 if (typeof expOrFn === 'function') {
  this.getter = expOrFn;
 } else {
  this.getter = parsePath(expOrFn);
 }
 this.value = this.lazy ? undefined : this.get();
};
Watcher.prototype.get = function get() {
 pushTarget(this);
 try {
  value = this.getter.call(vm, vm);
 } catch (e) {
  
 }
 return value
};
Dep.target = null;
var targetStack = [];
function pushTarget(target) {
 targetStack.push(target);
 Dep.target = target;
}

在构造函数Watcher最后会执行实例方法get,在实例方法get中执行pushTarget(this)中给Dep.target赋值的。

而依赖是在Vue页面或组件初次渲染时创建,所以产生的性能问题应该是首次渲染过慢的问题。

三、在何处滥用this去读取data中数据

在Dep.target存在时去执行这些滥用this去读取data中数据的代码会产生性能问题,故还要搞清楚这些代码是写在哪里才会被执行到,换句话来说,要搞清楚在哪里滥用this去读取data中数据会产生性能问题。

在第二小节中介绍了Dep.target被赋值后会执行value = this.getter.call(vm, vm),其中this.getter是一个函数,那么若在其中有用this去读取data数据,就会去收集依赖,假如滥用的话就会产生性能问题。

this.getter是在创建依赖过程中赋值的,每种依赖的this.getter都是不相同的。下面来一一介绍。

  • watch(侦听器)依赖的this.getterparsePath函数,其函数参数就是侦听的对象。
var bailRE = new RegExp(("[^" + (unicodeRegExp.source) + ".$_\\d]"));
function parsePath(path) {
 if (bailRE.test(path)) {
  return
 }
 var segments = path.split('.');
 return function(obj) {
  for (var i = 0; i < segments.length; i++) {
   if (!obj) {
    return
   }
   obj = obj[segments[i]];
  }
  return obj
 }
}

如下所示的代码中的 aa.b.c作为参数传入parsePath函数会返回一个函数赋值给this.getter,执行this.getter.call(vm, vm)会得到this.athis.a.b.c的值。在这个过程中不会存在滥用this去读取data中数据的场景。

watch:{
 a:function(newVal, oldVal){
 //做点什么
 }
}
vm.$watch('a.b.c', function (newVal, oldVal) {
 // 做点什么
})
  • computed(计算属性)依赖的this.getter有两种,如果计算属性的值是个函数,那么this.getter就是这个函数。如果计算属性的值是个对象,那么this.getter就是这个对象的get属性值,get属性值也是个函数。在这个函数可能会存在滥用this去读取data中数据的场景,举个例子,代码如下所示。
computed:{
 d:function(){
  let result = 0;
  for(let key in this.a){
   if(this.a[key].num > 20){
    result += this.a[key].num + this.b + this.c;
   }else{
    result += this.a[key].num + this.e + this.f;
   }
  }
  return result;
 }
}

在计算属性d中就存在滥用this去读取data数据。其中this.a是个数组,此时Dep.target的值为计算属性d这个依赖,在循环this.a中使用this去获取中a、b、c、e、f的数据,使这些数据进行一系列复杂的逻辑运算来重复地收集计算属性d这个依赖。导致获取计算属性d的值的速度变慢,从而产生性能问题。

  • 渲染Watcher的this.getter是一个函数如下所示:
updateComponent = function() {
 vm._update(vm._render(), hydrating);
};

其中vm._render()会把template模板生成的渲染函数render转成虚拟DOM(VNode):vnode = render.call(vm._renderProxy, vm.$createElement);,举一个例子来说明一下渲染函数render是什么。

例如template模板:

<template>
 <div class="wrap">
 <p>{{a}}<span>{{b}}</span></p>
 </div>
</template>

通过vue-loader会生成渲染函数render,如下所示:

(function anonymous() {
 with(this) {
  return _c('div', {
   attrs: {
    "class": "wrap"
   }
  }, [_c('p', [_v(_s(a)), _c('span', [_v(_s(b))])])])
 }
})

其中with语句的作用是为一个或一组语句指定默认对象,例with(this){ a + b } 等同 this.a + this.b,那么在template模板中使用{{ a }}相当使用this去读取data中的a数据。故在template模板生成的渲染函数render中也可能存在滥用this去读取data中数据的场景。举个例子,代码如下所示:

<template>
 <div class="wrap">
 <div v-for=item in list>
  <div> {{ arr[item.index]['name'] }} </div>
  <div> {{ obj[item.id]['age'] }} </div>
 </div>
 </div>
</template>

其中用v-for循环list数组过程中,不断用this去读取data中arr、obj的数据,使这些数据进行一系列复杂的逻辑运算来重复收集这个依赖,导致初次渲染的速度变慢,从而产生性能问题。

四、如何避免滥用this去读取data中数据

综上所述在计算属性和template模板中滥用this去读取data中数据会导致多次重复地收集依赖,从而产生性能问题,那要怎么避免这种情况。

  • 计算属性中如何避免

用ES6对象解构赋值来避免,计算属性的值是一个函数,其参数是Vue的实例化this对象,在上述计算属性中滥用this的例子中可以这样优化。

优化前:

computed:{
 d:function(){
  let result = 0;
  for(let key in this.a){
   if(this.a[key].num > 20){
    result += this.a[key].num + this.b + this.c;
   }else{
    result += this.a[key].num + this.e + this.f;
   }
  }
  return result;
 }
}

优化后:

computed: {
 d({ a, b, c, e, f }) {
 let result = 0;
 for (let key in a) {
  if (a[key].num > 20) {
  result += a[key].num + b + c;
  } else {
  result += a[key].num + e + f;
  }
 }
 return result;
 }
}

以上利用解构赋值提前把data数据中的a、b、c、e、f赋值给对应的变量a、b、c、e、f,然后在计算属性中可以通过这些变量访问data数据的,且不会触发data中对应数据的依赖收集。这样只用this读取了一次data中的数据,只触发了一次依赖收集,避免了多次重复地依赖收集产生的性能问题。

  • template模板中如何避免

提前处理v-for循环所用的数据,不要在v-for循环中去读取数组、对象类型的数据。在上述template模板中滥用this的例子中可以这样优化。

假设list、arr、obj皆是服务端返回来的数据,且arr和obj没有用到任何模块渲染中,可以这样优化。

优化前:

<template>
 <div class="wrap">
 <div v-for=item in list>
  <div> {{ arr[item.index]['name'] }} </div>
  <div> {{ obj[item.id]['age'] }} </div>
 </div>
 </div>
</template>

优化后:

<template>
 <div class="wrap">
 <div v-for=item in listData>
  <div{{item.name}} </div>
  <div>{{item.age}}</div>
 </div>
 </div>
</template>
<script>
const arr = [];
const obj = {}
export default {
 data() {
 return {
  list: [],
 }
 },
 computed: {
 listData: function ({list}) {
  list.forEach(item => {
  item.name = arr[item.index].name;
  item.age = obj[item.id].age;
  })
  return list;
 }
 },
}
</script>

以上就是Vue中避免滥用this去读取data中数据的详细内容,更多关于Vue中避免滥用this的资料请关注三水点靠木其它相关文章!

Vue.js 相关文章推荐
vue实现防抖的实例代码
Jan 11 Vue.js
vue二选一tab栏切换新做法实现
Jan 19 Vue.js
vue仿携程轮播图效果(滑动轮播,下方高度自适应)
Feb 11 Vue.js
基于vue-simple-uploader封装文件分片上传、秒传及断点续传的全局上传插件功能
Feb 23 Vue.js
详解vite+ts快速搭建vue3项目以及介绍相关特性
Feb 25 Vue.js
Vue如何实现组件间通信
May 15 Vue.js
详解Vue的列表渲染
Nov 20 Vue.js
Vue提供的三种调试方式你知道吗
Jan 18 Vue.js
vue打包时去掉所有的console.log
Apr 10 Vue.js
vue项目proxyTable配置和部署服务器
Apr 14 Vue.js
vue 把二维或多维数组转一维数组
Apr 24 Vue.js
vue实现登陆页面开发实践
May 30 Vue.js
vue脚手架项目创建步骤详解
Mar 02 #Vue.js
vue-cli中实现响应式布局的方法
Mar 02 #Vue.js
vue实现桌面向网页拖动文件的示例代码(可显示图片/音频/视频)
Mar 01 #Vue.js
vue 使用饿了么UI仿写teambition的筛选功能
Mar 01 #Vue.js
vue实现拖拽进度条
Mar 01 #Vue.js
vue 使用 v-model 双向绑定父子组件的值遇见的问题及解决方案
Mar 01 #Vue.js
vue前端和Django后端如何查询一定时间段内的数据
Feb 28 #Vue.js
You might like
解决php中Cannot send session cache limiter 的问题的方法
2007/04/27 PHP
对淘宝URL中ID提取的PHP代码
2013/09/01 PHP
PHP5.3安装Zend Guard Loader图文教程
2014/09/29 PHP
PHP实现动态柱状图改进版
2015/03/30 PHP
PHP基于单例模式实现的数据库操作基类
2016/01/15 PHP
PHP 获取客户端 IP 地址的方法实例代码
2018/11/11 PHP
PHP面向对象程序设计之对象克隆clone和魔术方法__clone()用法分析
2019/06/12 PHP
Yii 实现数据加密和解密
2021/03/09 PHP
用AJAX返回HTML片段中的JavaScript脚本
2010/01/04 Javascript
JavaScript常用对象的方法和属性小结
2012/01/24 Javascript
jquery scroll()区分横向纵向滚动条的方法
2014/04/04 Javascript
Javascript 实现图片无缝滚动
2014/12/19 Javascript
jQuery实现tab选项卡效果的方法
2015/07/08 Javascript
jQuery ajax方法传递中文时出现中文乱码的解决方法
2016/07/25 Javascript
JavaScript与java语言有什么不同
2016/09/22 Javascript
vue.js中$watch的用法示例
2016/10/04 Javascript
利用angular.copy取消变量的双向绑定与解析
2016/11/25 Javascript
vue-router 路由传参用法实例分析
2020/03/06 Javascript
[01:07:15]DOTA2-DPC中国联赛 正赛 DLG vs XG BO3 第二场 1月25日
2021/03/11 DOTA
[01:10:30]DOTA2-DPC中国联赛正赛 Dragon vs Dynasty BO3 第一场 3月4日
2021/03/11 DOTA
Python查看多台服务器进程的脚本分享
2014/06/11 Python
Linux环境下MySQL-python安装过程分享
2015/02/02 Python
Python while、for、生成器、列表推导等语句的执行效率测试
2015/06/03 Python
python之Socket网络编程详解
2016/09/29 Python
对python操作kafka写入json数据的简单demo分享
2018/12/27 Python
Python Selenium 之关闭窗口close与quit的方法
2019/02/13 Python
对PyQt5中树结构的实现方法详解
2019/06/17 Python
python列表切片和嵌套列表取值操作详解
2020/02/27 Python
jupyter notebook 使用过程中python莫名崩溃的原因及解决方式
2020/04/10 Python
python ETL工具 pyetl
2020/06/07 Python
解决阿里云邮件发送不能使用25端口问题
2020/08/07 Python
Canal官网:巴西女性时尚品牌
2019/10/16 全球购物
应用英语专业自荐信
2014/01/26 职场文书
离职员工给领导和同事的感谢信
2015/11/03 职场文书
关于感恩的作文
2019/08/26 职场文书
MongoDB orm框架的注意事项及简单使用
2021/06/20 MongoDB