vue中遇到的坑之变化检测问题(数组相关)


Posted in Javascript onOctober 13, 2017

最近在项目中遇到了一个问题,不知道为什么,所以最后通过动手做demo实践、查文档的方式解决了,这里做一个总结。

例1

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>vue</title>
 <script src="https://unpkg.com/vue@2.3.3/dist/vue.js"></script>
 <style>
  li:hover {
   cursor: pointer;
  }
 </style>
</head>
<body>
 <div class="wrap">
  <ul>
   <li v-for="item,index in items" v-on:click="handle(index)">
    <span>{{item.name}}</span>
    <span>{{numbers[index]}}</span>
   </li>
  </ul>
 </div>
 <script>
  var vm = new Vue({
   el: ".wrap",
   data: {
    numbers: [],
    items: [
     {name: 'jjj'},
     {name: 'kkk'},
     {name: 'lll'},
    ]
   },
   methods: {
    handle: function (index) {
     // WHY: 更新数据,view层未渲染,但通过console这个数组可以发现数据确实更新了
      if (typeof(this.numbers[index]) === "undefined" ) {
       this.numbers[index] = 1;
      } else {
       this.numbers[index]++;
      }
    }
   }
  });
 </script>
</body>
</html>

这里的实现目的很明确 --- 我希望在点击li时先检测是否存在,当然是不存在的,所以就将值设置为1, 如果再次点击,就让数字累加。

但是出现的问题是: 点击之后数字并没有在view层更新,而通过console打印发现数据确实更新了,只是view层没有及时的检测到, 而我一直以来的想法就是: 既然vue实现的时数据双向绑定,那么在model层发生了变化之后为什么就没有在view层更新呢? 

首先,我就考虑了这是不是数组的问题,于是,我测试了下面的例子:

例2

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>vue</title>
 <script src="https://unpkg.com/vue@2.3.3/dist/vue.js"></script>
 <style>
  li:hover {
   cursor: pointer;
  }
 </style>
</head>
<body>
 <div class="wrap">
  <ul>
   <li v-for="item,index in items" v-on:click="handle(index)">
    <span>{{item.name}}</span>
    <span>{{numbers[index]}}</span>
   </li>
  </ul>
 </div>
 <script>
  var vm = new Vue({
   el: ".wrap",
   data: {
    numbers: [],
    items: [
     {name: 'jjj'},
     {name: 'kkk'},
     {name: 'lll'},
    ]
   },
   methods: {
    handle: function (index) {
     // 不是数组,这里更新数据就可以直接在view层渲染
     this.items[index].name += " success";
    }
   }
  });
 </script>
</body>
</html>

这时,我再测试时就发现,这里的model层发生了变化时,view层就能及时、有效的得到更新。

而数组为什么不可以呢? 

于是在文档上的一个不起眼的地方找到了下面的说明:

vue中遇到的坑之变化检测问题(数组相关)

其中最重要的一句话就是 --- 如果对象是响应式的,确保属性被创建后也是响应式的,同时触发视图更新,这个方法主要用于避开Vue不能检测到属性被添加的限制。

那么什么情况下Vue是不能检测到属性被添加呢?  根据参考链接,我们在文档上看到了很好的说明 --- 深入响应式原理

首先,我们要了解Vue是如何实现数据的双向绑定的! 

把一个普通 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。Object.defineProperty 是仅 ES5 支持,且无法 shim 的特性,这也就是为什么 Vue 不支持 IE8 以及更低版本浏览器的原因。

知识补充:

访问器属性不包含数据值,他们包含一对getter函数和setter函数(这两个函数不是必须的)。在读取访问器属性时,会调用getter函数,这个函数负责返回有效的值;在写入访问器属性是,会调用setter函数并传入新值,这个函数负责决定如何处理数据。

访问器属性不能直接定义,必须是用Object.defineProperty()来定义。

下面是一个例子:

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>vue</title>
</head>
<body>
 <script>
  var book={
    _year:2004,
    edition:1
  };
  Object.defineProperty(book,"year",{
    get:function(){
      return this._year;
    },
    set:function(newValue){
      if(newValue>2004){
        this._year=newValue;
        this.edition+=newValue-2004;
      }
    }
  });
  console.log(book.year); // 2004 在读取访问器属性时会调用get函数
  book.year=2005; // 在给访问器属性赋值时会调用set函数
  console.log(book.edition); // 2
 </script>
</body>
</html>

这个例子应该可以很好的理解访问器属性了。

所以,当对象下的访问器属性值发生了改变之后(vue会将属性都转化为访问器属性,之前提到了), 那么就会调用set函数,这时vue就可以通过这个set函数来追踪变化,调用相关函数来实现view视图的更新。

每个组件实例都有相应的 watcher 实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件得以更新。

vue中遇到的坑之变化检测问题(数组相关)

即在渲染的过程中就会调用对象属性的getter函数,然后getter函数通知wather对象将之声明为依赖,依赖之后,如果对象属性发生了变化,那么就会调用settter函数来通知watcher,watcher就会在重新渲染组件,以此来完成更新。

OK!既然知道了原理,我们就可以进一步了解为什么出现了之前数组的问题了!

变化检测问题

收到现代JavaScript浏览器的限制,其实主要是 Object.observe() 方法支持的不好,Vue不能检测到对象的添加或者删除。然而Vue在初始化实例时就对属性执行了setter/getter转化过程,所以属性必须开始就在对象上,这样才能让Vue转化它。

所以对于前面的例子就不能理解了 --- 数组中index都可以看做是属性,当我们添加属性并赋值时,Vue并不能检测到对象中属性的添加或者删除,但是其的确是添加或删除了,故我们可以通过console看到变化,所以就没有办法做到响应式; 而在第二个例子中,我们是在已有的属性的基础上进行修改的,这些属性是在最开始就被Vue初始化实例时执行了setter/getter的转化过程,所以说他们的修改是有效的,model的数据可以实时的在view层中得到相应。

补充知识: 什么是 Object.observe() ?

在介绍之前,不得不残忍的说,尽管这个方法可以在某些浏览器上运行,但事实是这个方法已经废弃!

概述: 此方法用于异步地监视一个对象的修改。当对象的属性被修改时,方法的回调函数会提供一个有序的修改流,然而这个接口已经从各大浏览器移除,可以使用通用的proxy 对象。

方法:

Object.observe(obj, callback[, acceptList])

其中obj就是被监控的对象, callback是一个回调函数,其中的参数包括changes和acceptList,

changes一个数组,其中包含的每一个对象代表一个修改行为。每个修改行为的对象包含:

  • name: 被修改的属性名称。
  • object: 修改后该对象的值。
  • type: 表示对该对象做了何种类型的修改,可能的值为"add", "update", or "delete"。
  • oldValue: 对象修改前的值。该值只在"update"与"delete"有效。

acceptList在给定对象上给定回调中要监视的变化类型列表。如果省略, ["add", "update", "delete", "reconfigure", "setPrototype", "preventExtensions"] 将会被使用。

var obj = {
 foo: 0,
 bar: 1
};

Object.observe(obj, function(changes) {
 console.log(changes);
});

obj.baz = 2;
// [{name: 'baz', object: <obj>, type: 'add'}]

obj.foo = 'hello';
// [{name: 'foo', object: <obj>, type: 'update', oldValue: 0}]

delete obj.baz;
// [{name: 'baz', object: <obj>, type: 'delete', oldValue: 2}]

如上所示: 但是chrome也是不支持的,浏览器的兼容性如下:

vue中遇到的坑之变化检测问题(数组相关)

参考文档: Object.ovserve()

推荐阅读文章: Object.observe() 引爆数据绑定革命

解决方法

使用 Vue.set(object, key, value) 方法将响应属性添加到嵌套的对象上。 还可以使用 vm.$set 实例方法,这也是全局 Vue.set 方法的别名。

例3

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>vue</title>
 <script src="https://unpkg.com/vue@2.3.3/dist/vue.js"></script>
 <style>
  li:hover {
   cursor: pointer;
  }
 </style>
</head>
<body>
 <div class="wrap">
  <ul>
   <li v-for="item,index in items" v-on:click="handle(index)">
    <span>{{item.name}}</span>
    <span>{{numbers[index]}}</span>
   </li>
  </ul>
 </div>
 <script>
  var vm = new Vue({
   el: ".wrap",
   data: {
    numbers: [],
    items: [
     {name: 'jjj'},
     {name: 'kkk'},
     {name: 'lll'},
    ]
   },
   methods: {
    handle: function (index) {
     // WHY: 更新数据,view层未渲染,但通过console这个数组可以发现数据确实更新了
      if (typeof(this.numbers[index]) === "undefined" ) {
       this.$set(this.numbers, index, 1);
      } else {
       this.$set(this.numbers, index, ++this.numbers[index]);
      }
    }
   }
  });
 </script>
</body>
</html>

这样,我们就可以实现最终的目的了!

第二部分

上面一部分是指在data下的数组,而如果是在store中的数组,一般可以这样:

[ADD_ONE] (state, index) {
   if ( typeof state.numbers[index] == "undefined") {
    Vue.set(state.numbers, index, 1)
   } else {
    Vue.set(state.numbers, index, ++state.numbers[index])
   }
  }

即使用 Vue.set() 的方式来改变、增加。

注意:这里是确定index的增加和减少,所以用 Vue.set() 的方式

减的方式如下:

[REMOVE_ONE] (state, index) {
   Vue.set(state.numbers, index, --state.numbers[index]);
  }

第四部分

如果是在store的actions中我们需要对stroe中的数组进行填充,方法如下:

state内容:

kindnames: []

Mutations内容:

[ADD_KIND_NAME] (state, name) {
   state.kindnames.push(name);
  }

注意: 这里直接使用push的方式

当然,除了push,我们还可以shift等各种方式。 

actions的内容:

commit(ADD_KIND_NAME, state.items[index++].name);

这里,state.items[index++].name获取到的是一个一个的字符串。

最后,看到数据如下:

vue中遇到的坑之变化检测问题(数组相关)

注:同样可以参考文档 --- 细节与最佳实践

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
jQuery中使用了document和window哪些属性和方法小结
Sep 13 Javascript
调试Javascript代码(浏览器F12及VS中debugger关键字)
Jan 25 Javascript
JQuery入门——事件切换之hover()方法应用介绍
Feb 05 Javascript
Bootstrap每天必学之导航条(二)
Mar 01 Javascript
jquery UI Datepicker时间控件的使用及问题解决
Apr 28 Javascript
jQueryUI中的datepicker使用方法详解
May 25 Javascript
正则 js分转元带千分符号详解
Mar 08 Javascript
利用Javascript裁剪图片并存储的简单实现
Mar 13 Javascript
详解node-ccap模块生成captcha验证码
Jul 01 Javascript
vue.js中$set与数组更新方法
Mar 08 Javascript
微信小程序非swiper组件实现的自定义伪3D轮播图效果示例
Dec 11 Javascript
Vue 实现前进刷新后退不刷新的效果
Jun 14 Javascript
seaJs使用心得之exports与module.exports的区别实例分析
Oct 13 #Javascript
vue中axios处理http发送请求的示例(Post和get)
Oct 13 #Javascript
JavaScript实现随机数生成器(去重)
Oct 13 #Javascript
AngualrJs清除定时器遇到的坑
Oct 13 #Javascript
React Native中Navigator的使用方法示例
Oct 13 #Javascript
React Native中TabBarIOS的简单使用方法示例
Oct 13 #Javascript
ReactJS实现表单的单选多选和反选的示例
Oct 13 #Javascript
You might like
人大复印资料处理程序_查询篇
2006/10/09 PHP
php中文字母数字验证码实现代码
2008/04/25 PHP
利用PHP生成静态HTML文档的原理
2012/10/29 PHP
用PHP提取中英文词语以及数字的首字母的方法介绍
2013/04/23 PHP
解析如何在PHP下载文件名中解决乱码的问题
2013/06/20 PHP
Php中使用Select 查询语句的实例
2014/02/19 PHP
10个值得深思的PHP面试题
2016/11/14 PHP
PHP单例模式模拟Java Bean实现方法示例
2018/12/07 PHP
Nigma vs Alliance BO5 第四场2.14
2021/03/10 DOTA
DWR Ext 加载数据
2009/03/22 Javascript
鼠标事件延时切换插件
2011/03/12 Javascript
jquery中animate动画积累的解决方法
2013/10/05 Javascript
AngularJS基础 ng-keydown 指令简单示例
2016/08/02 Javascript
详解js中常规日期格式处理、月历渲染和倒计时函数
2016/12/28 Javascript
在一个页面实现两个zTree联动的方法
2017/12/20 Javascript
Javascript读写cookie的实例源码
2019/03/16 Javascript
[00:52]DOTA2国际邀请赛
2020/02/21 DOTA
[31:29]完美世界DOTA2联赛PWL S3 INK ICE vs Magma 第一场 12.20
2020/12/23 DOTA
python3.5 + PyQt5 +Eric6 实现的一个计算器代码
2017/03/11 Python
Python+Socket实现基于UDP协议的局域网广播功能示例
2017/08/31 Python
一篇文章彻底搞懂Python中可迭代(Iterable)、迭代器(Iterator)与生成器(Generator)的概念
2019/05/13 Python
python批量读取文件名并写入txt文件中
2020/09/05 Python
Windows 下更改 jupyterlab 默认启动位置的教程详解
2020/05/18 Python
Python操作MySQL数据库的示例代码
2020/07/13 Python
详解css3使用transform出现字体模糊的解决办法
2020/10/16 HTML / CSS
html5定制表单_动力节点Java学院整理
2017/07/11 HTML / CSS
Steiff台湾官网:德国金耳釦泰迪熊
2019/12/26 全球购物
法人委托书范本
2014/09/15 职场文书
运动会稿件100字
2014/09/24 职场文书
施工安全协议书范本
2014/09/26 职场文书
党的群众路线教育实践活动批评与自我批评发言稿
2014/10/16 职场文书
乡镇党的群众路线教育实践活动个人整改方案
2014/10/31 职场文书
民事赔偿协议书
2014/11/02 职场文书
2015大学自主招生自荐信范文
2015/03/04 职场文书
航班延误投诉信
2015/07/02 职场文书
Linux中文件的基本属性介绍
2022/06/01 Servers