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 相关文章推荐
ExtJs GridPanel简单的增删改实现代码
Aug 26 Javascript
Jquery中Ajax 缓存带来的影响的解决方法
May 19 Javascript
基于jquery的回到页面顶部按钮
Jun 27 Javascript
JS 跳转页面延迟2种方法
Mar 29 Javascript
『jQuery』取指定url格式及分割函数应用
Apr 22 Javascript
Js参数值中含有单引号或双引号问题的解决方法
Nov 06 Javascript
jQuery将所有被选中的checkbox某个属性值连接成字符串的方法
Jan 24 Javascript
jquery实现无限分级横向导航菜单的方法
Mar 12 Javascript
js实现随机抽选效果、随机抽选红色球效果
Jan 13 Javascript
JavaScript实现三级级联特效
Nov 05 Javascript
详解node字体压缩插件font-spider的用法
Sep 28 Javascript
vue 路由懒加载中给 Webpack Chunks 命名的方法
Apr 24 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通用检测函数集合
2011/02/08 PHP
利用phpExcel实现Excel数据的导入导出(全步骤详细解析)
2013/11/26 PHP
PHP FATAL ERROR: CALL TO UNDEFINED FUNCTION BCMUL()解决办法
2014/05/04 PHP
php简单socket服务器客户端代码实例
2015/05/18 PHP
JavaScript 学习笔记(六)
2009/12/31 Javascript
理解Javascript_07_理解instanceof实现原理
2010/10/15 Javascript
用js调用迅雷下载代码的二种方法
2013/04/15 Javascript
js中return false(阻止)的用法
2013/08/14 Javascript
jQuery中:enabled选择器用法实例
2015/01/04 Javascript
JavaScript通过元素索引号删除数组中对应元素的方法
2015/03/18 Javascript
JavaScript搜索字符串并将搜索结果返回到字符串的方法
2015/04/06 Javascript
原生js与jQuery实现简单的tab切换特效对比
2015/07/30 Javascript
简单实现js选项卡切换效果
2016/02/03 Javascript
PHP捕捉异常中断的方法
2016/10/24 Javascript
关于jQuery EasyUI 中刷新Tab选项卡后一个页面变形的解决方法
2017/03/02 Javascript
jQuery EasyUI 组件加上“清除”功能实例详解
2017/04/11 jQuery
详解node.js中的npm和webpack配置方法
2018/01/21 Javascript
vue2 前端搜索实现示例
2018/02/26 Javascript
Vue Object 的变化侦测实现代码
2020/04/15 Javascript
vue-cli脚手架的.babelrc文件用法说明
2020/09/11 Javascript
详解Typescript里的This的使用方法
2021/01/08 Javascript
浅谈Python的垃圾回收机制
2016/12/17 Python
python书籍信息爬虫实例
2018/03/19 Python
Python使用爬虫爬取静态网页图片的方法详解
2018/06/05 Python
对python内置map和six.moves.map的区别详解
2018/12/19 Python
使用Python向DataFrame中指定位置添加一列或多列的方法
2019/01/29 Python
css3 利用transform打造走动的2D时钟
2020/10/20 HTML / CSS
X/HTML5 和 XHTML2
2008/10/17 HTML / CSS
美国批发供应商:Kole Imports
2019/04/10 全球购物
当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?
2014/09/09 面试题
Servlet如何得到客户端机器的信息
2014/10/17 面试题
高中数学教学反思
2014/01/30 职场文书
教师校本培训方案
2014/02/26 职场文书
体育课外活动总结
2014/07/08 职场文书
责任书范本
2014/08/25 职场文书
写给媳妇的检讨书
2015/05/06 职场文书