Vue中数组变动监听

Vue的通过数据劫持的方式实现数据的双向绑定,即使用Object.defineProperty()来实现对属性的劫持,但是Object.defineProperty()中的setter是无法直接实现数组中值的改变的劫持行为的,想要实现对于数组下标直接访问的劫持需要使用索引对每一个值进行劫持,但是在Vue中考虑性能问题并未采用这种方式,所以需要特殊处理数组的变动。

描述

Vue是通过数据劫持的方式来实现数据双向数据绑定的,其中最核心的方法便是通过Object.defineProperty()来实现对属性的劫持,该方法允许精确地添加或修改对象的属性,对数据添加属性描述符中的gettersetter存取描述符实现劫持。

var obj = { __x: 1 };
Object.defineProperty(obj, "x", {set: function(x){ console.log("watch"); this.__x = x; },get: function(){ return this.__x; }
});
obj.x = 11; // watch
console.log(obj.x); // 11

而如果当劫持的值为数组且直接根据下标处理数组中的值时,Object.defineProperty()中的setter是无法直接实现数组中值的改变的劫持行为的,所以需要特殊处理数组的变动,当然我们可以对于数组中每一个值进行循环然后通过索引同样使用Object.defineProperty()进行劫持,但是在Vue中尤大解释说是由于性能代价和获得的用户体验收益不成正比,所以并没有使用这种方式使下标访问实现响应式,具体可以参阅githubVue源码的#8562

var obj = { __x: [1, 2, 3] };
Object.defineProperty(obj, "x", {set: function(x){ console.log("watch"); this.__x = x; },get: function(){ return this.__x; }
});
obj.x[0] = 11;
console.log(obj.x); // [11, 2, 3]
obj.x = [1, 2, 3, 4, 5, 6]; // watch
console.log(obj.x); // [1, 2, 3, 4, 5, 6]
obj.x.push(7);
console.log(obj.x); // [1, 2, 3, 4, 5, 6, 7]
// 通过下标对每一个值进行劫持
var obj = { __x: [1, 2, 3] };
Object.defineProperty(obj, "x", {set: function(x){ console.log("watch"); this.__x = x; },get: function(){ return this.__x; }
});
obj.x.forEach((v, i) => {Object.defineProperty(obj.x, i,{set:function(x) { console.log("watch"); v = x; },get: function(){ return v; }})
})
obj.x[0] = 11; // watch
console.log(obj.x); // [11, 2, 3]

Vue中对于数据是经过特殊处理的,对于下标直接访问的修改同样是不能触发setter,但是对于push等方法都进行了重写。

<!DOCTYPE html>
<html>
<head><title>Vue中数组变动监听</title>
</head>
<body><div id="app"></div>
</body>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
<script type="text/javascript">var vm = new Vue({el: '#app',data: {msg: [1, 2, 3]},template:`<div><div v-for="item in msg" :key="item">{{item}}</div><button @click="subscript">subscript</button><button @click="push">push</button></div>`,methods:{subscript: function(){this.msg[0] = 11;console.log(this.msg); // [11, 2, 3, __ob__: Observer]},push: function(){this.msg.push(4, 5, 6);console.log(this.msg); // [1, 2, 3, 4, 5, 6, __ob__: Observer]}}})
</script>
</html>

Vue中具体的重写方案是通过原型链来完成的,具体是通过Object.create方法创建一个新对象,使用传入的对象来作为新创建的对象的__proto__,之后对于特定的方法去拦截对数组的操作,从而实现对操作数组这个行为的监听。

// dev/src/core/observer/array.js
/** not type checking this file because flow doesn't play well with* dynamically accessing methods on Array prototype*/import { def } from '../util/index'const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)const methodsToPatch = ['push','pop','shift','unshift','splice','sort','reverse'
]/*** Intercept mutating methods and emit events*/
methodsToPatch.forEach(function (method) {// cache original methodconst original = arrayProto[method]def(arrayMethods, method, function mutator (...args) {const result = original.apply(this, args)const ob = this.__ob__let insertedswitch (method) {case 'push':case 'unshift':inserted = argsbreakcase 'splice':inserted = args.slice(2)break}if (inserted) ob.observeArray(inserted)// notify changeob.dep.notify()return result})
})

处理方法

重赋值

Object.defineProperty()方法无法劫持对于数组值下标方式访问的值的改变,这样的话就需要避免这种访问,可以采用修改后再赋值的方式,也可以采用数组中的一些方法去形成一个新数组,数组中不改变原数组并返回一个新数组的方法有sliceconcat等方法以及spread操作符,当然也可以使用map方法生成新数组,此外在Vue中由于重写了splice方法,也可以使用splice方法进行视图的更新。

var obj = { __x: [1, 2, 3] };
Object.defineProperty(obj, "x", {set: function(x){ console.log("watch"); this.__x = x; },get: function(){ return this.__x; }
});obj.x[0] = 11;
obj.x = obj.x; // watch
console.log(obj.x); // [11, 2, 3]obj.x[0] = 111;
obj.x = [].concat(obj.x); // watch
console.log(obj.x); // [111, 2, 3]obj.x[0] = 1111;
obj.x = obj.x.slice(); // watch
console.log(obj.x); // [1111, 2, 3]obj.x[0] = 11111;
obj.x = obj.x.splice(0, obj.x.length); // watch
console.log(obj.x); // [11111, 2, 3]

Proxy

Vue3.0使用Proxy实现数据劫持,Object.defineProperty只能监听属性,而Proxy能监听整个对象,通过调用new Proxy(),可以创建一个代理用来替代另一个对象被称为目标,这个代理对目标对象进行了虚拟,因此该代理与该目标对象表面上可以被当作同一个对象来对待。代理允许拦截在目标对象上的底层操作,而这原本是Js引擎的内部能力,拦截行为使用了一个能够响应特定操作的函数,即通过Proxy去对一个对象进行代理之后,我们将得到一个和被代理对象几乎完全一样的对象,并且可以从底层实现对这个对象进行完全的监控。

var target = [1, 2, 3];
var proxy = new Proxy(target, {set: function(target, key, value, receiver){ console.log("watch");return Reflect.set(target, key, value, receiver);},get: function(target, key, receiver){ return target[key];}
});
proxy[0] = 11; // watch
console.log(target); // [11, 2, 3]

每日一题

https://github.com/WindrunnerMax/EveryDay

参考

https://zhuanlan.zhihu.com/p/50547367
https://github.com/vuejs/vue/issues/8562
https://juejin.im/post/6844903699425263629
https://juejin.im/post/6844903597591773198
https://segmentfault.com/a/1190000015783546
https://cloud.tencent.com/developer/article/1607061
https://www.cnblogs.com/tugenhua0707/p/11754291.html
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy

Vue中数组变动监听相关推荐

  1. vue中使用watch监听$route 无效问题

    vue 中使用watch监听$route失效问题!在watch里面监听$route变化,发现并没有监听到. 路由: {name: 'secondUser ',component: secondUser ...

  2. Vue 中使用watch监听$route 无效问题

    Vue 中使用watch监听$route失效问题! 今天在项目操作中发现一个问题,在watch里面监听$route变化,发现并没有监听到,查阅了一些资料最终解决,现写出与大家共同分享,也忘出现此问题的 ...

  3. vue原理:vue中是如何监听数组变化?

    我们知道通过Object.defineProperty()劫持数组为其设置getter和setter后,调用的数组的push.splice.pop等方法改变数组元素时并不会触发数组的setter,这就 ...

  4. vue中如何深度监听一个对象?

    大家都知道,Vue项目中对数据的监听,提供了一个很好的钩子watch,watch可以极其方便的监听我们常用数据类型值的变化,但通常当我们想监听一个对象中,某个属性值的变化时,很难达到我们预期的效果.那 ...

  5. Vue响应式原理Vue中数据的监听

    文章目录 Observer理解如上图 Dep「依赖管理」 Watcher理解如上图 总结:Vue响应式原理的核心就是Observer.Dep.Watcher. Vue响应式原理理解以后就知道Vue是怎 ...

  6. vue中的watch监听数据变化

    watch:{//监控选项卡,自动切换页眉activeName: {handler(){if(this.activeName === 'first'){this.listPageMenuName='知 ...

  7. vue 监听map数组变化_vuex state中的数组变化监听实例

    前言 首先,因为我有一个需求就是vue组件中有一组多选框,选中多选框的内容,要在另一个组件中进行视图更新,这个就设计的兄弟组件之间的通信了,兄弟组件之前通信我首先选用的vuex这个解决办法. 问题 v ...

  8. 利用vue进行页面滚动监听,上拉刷新

    2019独角兽企业重金招聘Python工程师标准>>> 1.利用vue进行页面滚动监听,上拉刷新 methods: {handleScroll(){let page = docume ...

  9. Vue watch如何同时监听多个属性?

    vue watch如何同时监听多个属性 第一种方法 data () {return {name1: '',name2: '',age: ''}},watch: {'name1': function ( ...

最新文章

  1. Iterator(迭代器)的一般用法
  2. python中字符串(比如文件路径)前面有一个r什么意思
  3. Web服务器指纹识别工具httprint
  4. Spark之数据倾斜 --采样分而治之解决方案
  5. c语言中数据存储在文件中,急求如何将下列C语言程序数据存储到文件中?
  6. 最长配对(51Nod-2494)
  7. 一些牛逼的Python程序整理
  8. 10-ret/retf+call+mul
  9. GX Works2 安装详细过程
  10. 对Tabular方法的总结
  11. html5广告拦截器识别代码做提示(本站内容无法显示)
  12. cv2.cvtColor报错
  13. C#: 数字经纬度和度分秒经纬度间的转换
  14. 简述java重载和重写的相同点_方法的重载和重写的异同之处
  15. java怎么写合并列sql_SQL STUFF函数 拼接字符串 多列 合并成一列 转
  16. Oracle 执行计划(Explain Plan)
  17. 在博客和Markdown和Python中自由添加emoji!包括博客标题和Python打印!
  18. 有趣的小项目:半个指头大的收音机制作成功 单片机+RDA5807源程序
  19. html中富田文本,富田桥曾氏游浆豆腐制作技艺
  20. C语言——查找(折半、分块、二叉排序、哈希法)

热门文章

  1. Open vSwitch 概述
  2. Eureka实例自动过期
  3. 关于设计RPC框架的几个问题
  4. java模拟多线程买票问题
  5. vtkSuperquadricSource:创建以原点为中心的多边形超二次曲面
  6. CentOS6.9快速安装配置svn
  7. VSS新建项目后导致项目组成员不能打开解决方案的解决方法
  8. 利用zabbix web scenario 监控Web站点的可用性
  9. NandFlash启动理解(S3C2410)
  10. 通过Redis的Pub/Sub实现对服务器群的监控管理