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中的$nextTick
Dec 24 Vue.js
Vue 修改网站图标的方法
Dec 31 Vue.js
基于VUE实现简单的学生信息管理系统
Jan 13 Vue.js
Vue单页面应用中实现Markdown渲染
Feb 14 Vue.js
vue登录页实现使用cookie记住7天密码功能的方法
Feb 18 Vue.js
vue2实现provide inject传递响应式
May 21 Vue.js
vue项目多环境配置(.env)的实现
Jul 21 Vue.js
Vue如何清空对象
Mar 03 Vue.js
关于Vue中的options选项
Mar 22 Vue.js
vue实现拖拽交换位置
Apr 07 Vue.js
vue中this.$http.post()跨域和请求参数丢失的解决
Apr 08 Vue.js
Vue ECharts实现机舱座位选择展示功能
May 15 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简单数据库操作类实例【支持增删改查及链式操作】
2016/10/10 PHP
php获取ajax的headers方法与内容实例
2017/12/27 PHP
php正确输出json数据的实例讲解
2018/08/21 PHP
Thinkphp5框架实现获取数据库数据到视图的方法
2019/08/14 PHP
让任务管理器中的CPU跳舞的js代码
2008/11/01 Javascript
JQuery 无废话系列教程(一) jquery入门 [推荐]
2009/06/23 Javascript
Jquery颜色选择器ColorPicker实现代码
2012/11/14 Javascript
javascript游戏开发之《三国志曹操传》零部件开发(五)可移动地图的实现
2013/01/23 Javascript
js动态生成指定行数的表格
2013/07/11 Javascript
js+html5操作sqlite数据库的方法
2016/02/02 Javascript
基于jQuery实现顶部导航栏功能
2016/12/27 Javascript
微信小程序的动画效果详解
2017/01/18 Javascript
使用Bootstrap + Vue.js实现添加删除数据示例
2017/02/27 Javascript
gulp解决跨域的配置文件问题
2017/06/08 Javascript
Angular中的$watch方法详解
2017/09/18 Javascript
浅谈ajax在jquery中的请求和servlet中的响应
2018/01/22 jQuery
vue-router 源码之实现一个简单的 vue-router
2018/07/02 Javascript
JS变量提升及函数提升实例解析
2020/09/03 Javascript
JavaScript 闭包的使用场景
2020/09/17 Javascript
python使用scrapy解析js示例
2014/01/23 Python
python对数组进行反转的方法
2015/05/20 Python
python用BeautifulSoup库简单爬虫实例分析
2018/07/30 Python
python3 实现一行输入,空格隔开的示例
2018/11/14 Python
关于jupyter打开之后不能直接跳转到浏览器的解决方式
2020/04/13 Python
python3爬虫中多线程的优势总结
2020/11/24 Python
Selenium获取登录Cookies并添加Cookies自动登录的方法
2020/12/04 Python
H5新属性audio音频和video视频的控制详解(推荐)
2016/12/09 HTML / CSS
WINDOWS域的具体实现方式是什么
2014/02/20 面试题
中学生运动会入场词
2014/02/12 职场文书
副总经理岗位职责
2014/03/16 职场文书
《有趣的发现》教学反思
2014/04/15 职场文书
2014年秋季开学寄语
2014/08/02 职场文书
2014年化工厂工作总结
2014/11/25 职场文书
企业愿景口号
2015/12/25 职场文书
2016年情人节广告语
2016/01/28 职场文书
使用pandas生成/读取csv文件的方法实例
2021/07/09 Python