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表单验证插件formValidator(改进版)
Feb 03 Javascript
JavaScript常用函数工具集:lao-utils
Mar 01 Javascript
jQuery插件passwordStrength密码强度指标详解
Jun 24 Javascript
js HTML5上传示例代码完整版
Oct 10 Javascript
一篇文章搞定JavaScript类型转换(面试常见)
Jan 21 Javascript
详解JavaScript RegExp对象
Feb 04 Javascript
微信小程序 支付功能实现PHP实例详解
May 12 Javascript
详谈for循环里面的break和continue语句
Jul 20 Javascript
Webpack框架核心概念(知识点整理)
Dec 22 Javascript
Vuex的actions属性的具体使用
Apr 14 Javascript
javascript实现自由编辑图片代码详解
Jun 21 Javascript
cordova+vue+webapp使用html5获取地理位置的方法
Jul 06 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
php探针不显示内存解决方法
2019/09/17 PHP
PHP连接MySQL数据库三种实现方法
2020/12/10 PHP
利用javascript的面向对象的特性实现限制试用期
2011/08/04 Javascript
js代码实现无缝滚动(文字和图片)
2015/08/20 Javascript
全面解析JavaScript里的循环方法之forEach,for-in,for-of
2020/04/20 Javascript
第二篇Bootstrap起步
2016/06/21 Javascript
BootStrap+Angularjs+NgDialog实现模式对话框
2016/08/24 Javascript
微信小程序教程之本地图片上传(leancloud)实例详解
2016/11/16 Javascript
angularJS+requireJS实现controller及directive的按需加载示例
2017/02/20 Javascript
d3.js实现立体柱图的方法详解
2017/04/28 Javascript
JS原生轮播图的简单实现(推荐)
2017/07/22 Javascript
通过js控制时间,一秒一秒自己动的实例
2017/10/25 Javascript
js实现通过开始结束控制的计时器
2019/02/25 Javascript
详解微信小程序调用支付接口支付
2019/04/28 Javascript
一次让你了解全部JavaScript的作用域
2019/06/24 Javascript
js实现踩五彩块游戏
2020/02/08 Javascript
Vue常用的全选/反选的示例代码
2020/02/19 Javascript
react基本安装与测试示例
2020/04/27 Javascript
基于javascript实现移动端轮播图效果
2020/12/21 Javascript
python递归计算N!的方法
2015/05/05 Python
python字符串str和字节数组相互转化方法
2017/03/18 Python
Windows下python3.7安装教程
2018/07/31 Python
python pandas消除空值和空格以及 Nan数据替换方法
2018/10/30 Python
详解python中的模块及包导入
2019/08/30 Python
Python实现AES加密,解密的两种方法
2020/10/03 Python
html5 拖拽及用 js 实现拖拽功能的示例代码
2020/10/23 HTML / CSS
英国日常交易网站:Wowcher
2018/09/04 全球购物
澳大利亚女装精品店:Alannah Hill
2020/07/29 全球购物
后勤人员自我鉴定
2013/10/20 职场文书
2014年大学生村官工作总结
2014/11/19 职场文书
大学生求职简历自我评价
2015/03/02 职场文书
2015年绩效考核工作总结
2015/05/23 职场文书
保险公司增员口号
2015/12/25 职场文书
详解Go与PHP的语法对比
2021/05/29 PHP
pandas数值排序的实现实例
2021/07/25 Python
JS实现九宫格拼图游戏
2022/06/28 Javascript