Vue中数组变动监听
Vue中数组变动监听
Vue的通过数据劫持的方式实现数据的双向绑定,即使用Object.defineProperty()来实现对属性的劫持,但是Object.defineProperty()中的setter是无法直接实现数组中值的改变的劫持行为的,想要实现对于数组下标直接访问的劫持需要使用索引对每一个值进行劫持,但是在Vue中考虑性能问题并未采用这种方式,所以需要特殊处理数组的变动。
描述
Vue是通过数据劫持的方式来实现数据双向数据绑定的,其中最核心的方法便是通过Object.defineProperty()来实现对属性的劫持,该方法允许精确地添加或修改对象的属性,对数据添加属性描述符中的getter与setter存取描述符实现劫持。
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中尤大解释说是由于性能代价和获得的用户体验收益不成正比,所以并没有使用这种方式使下标访问实现响应式,具体可以参阅github中Vue源码的#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 method
const original = arrayProto[method]
def(arrayMethods, method, function mutator (...args) {
const result = original.apply(this, args)
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
if (inserted) ob.observeArray(inserted)
// notify change
ob.dep.notify()
return result
})
})
处理方法
重赋值
Object.defineProperty()方法无法劫持对于数组值下标方式访问的值的改变,这样的话就需要避免这种访问,可以采用修改后再赋值的方式,也可以采用数组中的一些方法去形成一个新数组,数组中不改变原数组并返回一个新数组的方法有slice、concat等方法以及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中数组变动监听的更多相关文章
- 在vue中使用watch监听对象或数组
最近发现在vue中使用watch监听对象或者数组时,当数组或者对象只是单一的值改变时,并不会出发watch中的事件. 在找问题过程中,发现当数组使用push一类的方法时,会触发watch,如果只是单一 ...
- vue中的数据监听以及数据交互
现在我们来看一下vue中的数据监听事件$watch, js代码: new Vue({ el:"#div", data:{ arr:[,,] } }).$watch("ar ...
- vue中的事件监听之——v-on vs .$on
跟着视频中老师的教学视频学vue的时候,看很多时候都用@(v-on)来监听子级emit的自定义事件,但在bus总线那块,又用.$on来监听bus自身emit的事件,v-on之间似乎相似但又不同,今天对 ...
- vue中如何深度监听一个对象?
大家都知道,Vue项目中对数据的监听,提供了一个很好的钩子watch,watch可以极其方便的监听我们常用数据类型值的变化,但通常当我们想监听一个对象中,某个属性值的变化时,很难达到我们预期的效果.那 ...
- vue中watch深度监听
监听基本类型的都是浅度监听 watch的深度监听,监听复杂类型都是深度监听(funciton ,arrat ,object) // 监听对象 data(){ return { a:{ b:, c: } ...
- vue中数组变动更新检测
Vue 包含两种观察数组的方法分别如下 1.变异方法 顾名思义,变异方法会改变被这些方法调用的原始数组,它们也将会触发视图更新,这些方法如下 push() pop() shift() unshift( ...
- vue 内存数组变化监听
watch: { carts: { handler(val, oldVal) { subtotal(this.carts); console.log(this.carts) }, deep: true ...
- vue中输入框事件监听 v-on:input
<van-field v-model="inputVal" v-on:input="search" />
- vue中关于对象的监听与数组的监听
数组: 数组可监听到的方法:'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' 如果是根据索引改变值,需要使用vue.$set ...
- Fragment中的按键监听
在Fragmentzhong中写按键监听,有两处处需要注意: 1)是否是当前显示的fragment:. 2)在所依托的activity中的onKeyDown方法处理监听事件: 其他地方和普通按键监听一 ...
随机推荐
- ubuntu-软件管理工具-apt
- 银河麒麟上面 ntopng的安装与使用
银河麒麟上面 ntopng的安装与使用 背景 一直想用Grafana监控网络流量 但是断断续续尝试了一周的时间都没有搞定. 发现这一块已经进入了瓶颈. 比较无奈的情况下回到了原来的iftop/iptr ...
- Nginx arm编译安装
Nginx arm编译安装 背景 计划编译一套产品. 能够比较方便快捷的进行 nginx的交付. 主要思想是源码编译 不仅能够在arm上面运行 也可以在x86上面编译 考虑性能还有一些扩展性. 高效处 ...
- 近十年CPU性能提升效果分析
近十年CPU性能提升效果分析 测试脚本 ./redis-server redis.conf cpu=`cat /proc/cpuinfo |grep "model name" |h ...
- Python学习之十_paramiko的简单学习
Python学习之十_paramiko的简单学习 简介 pywinrm 是python用于连接访问windows的工具 paramiko 是python用于连接访问linux的工具 ansible等工 ...
- [转帖]IO多路复用的三种机制Select,Poll,Epoll
I/O多路复用(multiplexing)的本质是通过一种机制(系统内核缓冲I/O数据),让单个进程可以监视多个文件描述符,一旦某个描述符就绪(一般是读就绪或写就绪),能够通知程序进行相应的读写操作 ...
- [转帖]Oracle入门精读28-字符集 AL32UTF8与UTF8
字符(Character) 字符是各种文字和符号的总称,包括各国家文字.标点符号.图形符号.数字等. 字符编码(Character Encoding) 是一套法则,使用该法则能够对自然语言的字符的一个 ...
- reposync与createrepo创建离线yum源的方法
背景 昨天晚上进行了在线升级银河麒麟V10SP2的audit和mate-indicator的rpm包 今天想了下,如果机器无法上网. 必须得在公司内部搭建一套离线的rpm源进行处理 想了下还是使用re ...
- Docker搭建SvnServer
下载svn-server官方镜像 docker pull garethflowers/svn-server 运行svn-server容器 docker run -v /home/svn:/var/op ...
- Gin 项目引入热加载
目录 一.什么是热加载 二.Air 2.1 介绍 2.2 特性 特性: 2.3 相关文档 2.4 安装 推荐使用 install.sh 使用 go install 2.5 配置环境变量 2.6 使用 ...