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 相关文章推荐
js中访问html中iframe的文档对象的代码[IE6,IE7,IE8,FF]
Jan 08 Javascript
js类定义函数时用prototype与不用的区别示例介绍
Jun 10 Javascript
AngularJs实现ng1.3+表单验证
Dec 10 Javascript
基于Javascript实现倒计时功能
Feb 22 Javascript
Bootstrap 布局组件(全)
Jul 18 Javascript
基于d3.js实现实时刷新的折线图
Aug 03 Javascript
JavaScript中附件预览功能实现详解(推荐)
Aug 15 Javascript
微信小程序授权获取用户详细信息openid的实例详解
Sep 20 Javascript
浅谈js的解析顺序 作用域 严格模式
Oct 23 Javascript
微信小程序获取音频时长与实时获取播放进度问题
Aug 28 Javascript
JS多个异步请求 按顺序执行next实现解析
Sep 16 Javascript
swiper4实现移动端导航栏tab滑动切换
Oct 16 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结束标签的使用细节探讨及联想
2013/03/04 PHP
PHP远程采集图片详细教程
2014/07/01 PHP
浅析PHP中的i++与++i的区别及效率
2016/06/15 PHP
PHP 二级子目录(后台目录)设置二级域名
2017/03/02 PHP
php curl简单采集图片生成base64编码(并附curl函数参数说明)
2019/02/15 PHP
PHP手机号码及邮箱正则表达式实例解析
2020/07/11 PHP
JavaScript 不只是脚本
2007/05/30 Javascript
input、button的不同type值在ajax提交表单时导致的陷阱
2009/02/24 Javascript
浅谈Javascript Base64 加密解密
2014/12/28 Javascript
JS中使用mailto实现将用户在网页中输入的内容传递到本地邮件客户端
2016/10/08 Javascript
JS如何设置iOS中微信浏览器的title
2016/11/22 Javascript
JavaScript登录验证基础教程
2017/11/01 Javascript
用Node提供静态文件服务的方法
2018/07/06 Javascript
vue中el-upload上传图片到七牛的示例代码
2018/10/19 Javascript
vue中封装axios并实现api接口的统一管理
2020/12/25 Vue.js
Javascript生成器(Generator)的介绍与使用
2021/01/31 Javascript
pandas DataFrame 根据多列的值做判断,生成新的列值实例
2018/05/18 Python
python爬虫 爬取58同城上所有城市的租房信息详解
2019/07/30 Python
python实现的爬取电影下载链接功能示例
2019/08/26 Python
Python如何基于rsa模块实现非对称加密与解密
2020/01/03 Python
K最近邻算法(KNN)---sklearn+python实现方式
2020/02/24 Python
改变 Python 中线程执行顺序的方法
2020/09/24 Python
基于Python的接口自动化读写excel文件的方法
2021/01/15 Python
关于h5中的fetch方法解读(小结)
2017/11/15 HTML / CSS
Nike比利时官网:Nike.com (BE)
2019/02/07 全球购物
世界领先的电子书网站:eBooks.com(在线购买小说、非小说和教科书)
2019/03/30 全球购物
远程教育心得体会
2014/01/03 职场文书
施工班组长岗位职责
2014/01/05 职场文书
优秀导游先进事迹材料
2014/01/25 职场文书
小学生新年寄语
2014/04/03 职场文书
买卖车协议书
2014/04/21 职场文书
办公室主任个人对照检查材料思想汇报
2014/10/11 职场文书
事业单位个人查摆问题及整改措施
2014/10/28 职场文书
单位车辆管理制度
2015/08/05 职场文书
解决Nginx 配置 proxy_pass 后 返回404问题
2021/03/31 Servers
Python-OpenCV教程之图像的位运算详解
2021/06/21 Python