vue watch数组引发的血案
栏目: JavaScript · 发布时间: 7年前
data () {
return {
nameList: ['jiang', 'ru', 'yi']
}
},
methods: {
handleClick () {
// 通过push,unshift等方法改变数组可以通过watch监听到
this.nameList.push('瑶')
// 直接通过数组下标进行修改数组无法通过watch监听到
this.nameList[2] = '爱'
// 通过$set修改数组可以通过watch监听到
this.$set(this.nameList, 2, '张')
// 利用数组splice方法修改数组可以通过watch监听到
this.nameList.splice(2, 1, '蒋如意')
}
},
watch: {
nameList (newVal) {
console.log(newVal)
}
}
复制代码
总结
变异方法
Vue包含一组观察数组的变异方法,所以它们也将会触发视图更新,这些方法如下:
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
替换数组
变异方法,顾名思义,会改变被这些方法调用的原始数组。相比之下,也有非变异方法,例如:filter(),concat()和slice()。这些不会改变原始数组,但总是返回一个新数组。当使用非变异方法时,可以用新数组替换就数组
注意事项
由于JavaScript的限制,Vue不能检测以下变动的数组
1.当你利用索引直接设置一个项时,例如:vm.items[index] = newValue
2.当你修改数组的长度时,例如:vm.items.length = newLength
为了解决第一类问题,以下两种方式可以实现
// 方法一 Vue.set(vm.items, index, newValue) Vue.splice(index, 1, newValue) 复制代码
为了解决第二类问题,可以使用splice
vm.items.splice(newLength) 复制代码
小发现:通过下标直接更改数组元素,无法触发渲染机制更新视图,但此时数组的值已经发生变化,若同时有其他数据更改导致重新渲染时,绑定数组的dom也会更新显示最新的数据
通过vue表象解释
- vue在对数据监听时,需要数据在初始化的时候就已经确定属性的key,通过Object.defineProperty进行数据劫持,从而实现在数据发生变化时触发视图更新,例如:
obj: {
name: '蒋',
age: '28'
}
复制代码
name和age两个属性从初始化的时候就已经确定了,此时更改obj中的两个属性值是可以被监听到并且触发视图更新的; 如果通过js代码对obj对象添加一个新属性,那么当这个属性发生变化时是无法被监听到的,除非使用this.$set方法添加的新对象; 2. 数组也是一个对象,索引相当于对象属性的key值,但是vue在针对单一的数组时,是没有对该索引对应的值进行数据劫持的,所以直接更改数组元素的值无法被监听到, 并且不能触发视图更新,例如:
arr1: [1, 2, 3, 4];
通过arr1[0] = 666,无法被监听到
arr2: [
{
name: 'a'
},
{
name: 'b'
}
]
arr2[0].name = 'cc';
复制代码
此时的更改是可以被监听到,并且触发视图更新的
我的疑问: 为什么vue不对单一的数组元素进行数据劫持呢,亲测可以通过数据劫持的方式来触发set方法
// 我的测试方式如下
------------- def开始 -----------------
function def (obj, key, val) {
var value = val
Object.defineProperty(obj, key, {
set (newVal) {
console.log('触发set')
value = newVal
},
get () {
return value
}
})
}
-------------- def结束 ----------------
var arr = [1, 2, 3]
arr.forEach((item, index) => {
def(arr, index, item)
})
arr[0] = 11
arr[1] = 22
console.log(arr) // [11, 22, 3]
-----------------------------
var obj = {
list: ['a', 'b', 'c']
}
obj.list.forEach((item, index) => {
def(obj.list, index, item)
})
obj.list[0] = 'jiang'
obj.list[1] = 'ru'
console.log(obj.list) // ['jiang', 'ru', 'c']
复制代码
通过源码层面解释
// 由于浏览器兼容问题,Object.observe方法不能起到监听数据变动,所以vue在实现的过程中自己有封装了Observe类
- Observer 类的 constructor 方法中对需要被监听的值进行了判断
- 如果该值为数组,那么需要调用 observeArray 方法去处理
- observeArray 方法中主要是遍历数组中每个元素,并且调用 observe 方法去处理每个元素。
- observe 方法做的事情就是,如果该元素为简单的字符串或者数字则不做任何处理,直接return;若该元素为对象的话则调用
new Observer(value)方法去处理该元素,就返回到最先类继续往下走; 从第4步就能发现为什么通过索引改动数组的元素无法触发视图更新了 - 回到 Observer,如果判断需要被监听的值不为数组,则调用walk方法,处理该元素
- walk 方法中调用
Object.keys()方法来遍历对象,并且调用defineReactive(obj, keys[i])方法 - defineReactive 方法做的事情就是利用
Object.defineProperty()方法去监听对象中的每个属性; - 在 set 方法中会去调用
dep.notify()方法,该方法就是去通知watcher触发update方法去重新渲染视图; - 在get方法中会将该属性添加到相关的依赖中
源码
// 由于浏览器兼容问题, Object.observe 方法不能起到监听数据变动,所以vue在实现的过程中自己有封装了 Observe 类
- Observer类的 constructor 方法中对需要被监听的值进行了判断
- 如果该值为数组,那么需要调用 observeArray 方法去处理
- observeArray方法中主要是遍历数组中每个元素,并且调用observe方法去处理每个元素。
- observe方法做的事情就是,如果该元素为简单的字符串或者数字则不做任何处理,直接return;若该元素为对象的话则调用new Observer(value)方法去处理该元素,就返回到最先类继续往下走;
5. 回到Observer,如果判断需要被监听的值不为数组,则调用walk方法,处理该元素。
6. walk方法中调用Object.keys()方法来遍历对象,并且调用defineReactive(obj, keys[i])方法
7. defineReactive方法做的事情就是利用Object.defineProperty()方法去监听对象中的每个属性;
8. 在set方法中会去调用dep.notify()方法,该方法就是去通知watcher触发update方法去重新渲染视图;
9. 在get方法中会将该属性添加到相关的依赖中
怎样通过watch来监听一个数组
// 例一:一个简单的数组
data () {
return {
dataList: [1, 2, 3, 4]
}
},
methods: {
handleClick () {
this.dataList.forEach((item, index) => {
// 首先这里通过遍历数组改变元素的值,不能直接进行赋值更改,否则无法被监听到
// item = '你好'
// 需要用$set方法进行赋值
this.$set(this.dataList, index, '你好')
})
}
},
watch: {
dataList (newVal) {
console.log(newVal) // ['你好', '你好', '你好', '你好']
}
}
// 例二: 一个对象数组
data () {
return {
dataList: [
{
label: '一年级',
status: '上课'
},
{
label: '二年级',
status: '上课'
},
{
label: '三年级',
status: '上课'
},
{
label: '四年级',
status: '上课'
},
{
label: '五年级',
status: '上课'
},
{
label: '六年级',
status: '上课'
}
]
}
},
methods: {
handleClick () {
// 如果是对象数组,可以通过这种方法改变元素的值,并且能够触发视图更行
this.dataList.forEach(item => {
item.status = '下课'
})
}
},
watch: {
// dataList (newVal) { // 无法监听到数组变化
// newVal.forEach(item => {
// console.log(item.status)
// })
// },
dataList: { // 通过设置deep的值可以监听到
handler () {
newVal.forEach(item => {
console.log(item.status) // '下课', '下课', '下课', '下课', '下课', '下课'
})
},
deep: true
}
}
复制代码
通过上述例子可以发现:
- 对于一个单一简单的数组,如果需要更改里面元素的值时,需要通过this.$set方法进行更改,此时可以被监听到,并且触发视图更新
- 需要强调的一点,如果简单的数组不是通过this.$set方法更改的那么不管watch中是否设置deep:true都没有用,无法监听到数组发生的变化
- 通过例二可以发现,对象数组中,每个对象中的元素可以直接进行更改并且能够触发视图更新,但是如果需要通过watch来监听这个数组是否发生变化,则必须加上deep:true
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Cyberwar
Kathleen Hall Jamieson / Oxford University Press / 2018-10-3 / USD 16.96
The question of how Donald Trump won the 2016 election looms over his presidency. In particular, were the 78,000 voters who gave him an Electoral College victory affected by the Russian trolls and hac......一起来看看 《Cyberwar》 这本书的介绍吧!