快捷搜索: 长连接 前端 源码 pan

vue watch数组引发的血案

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表象解释

  1. 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类

  1. Observer 类的 constructor 方法中对需要被监听的值进行了判断
  2. 如果该值为数组,那么需要调用 observeArray 方法去处理
  3. observeArray 方法中主要是遍历数组中每个元素,并且调用 observe 方法去处理每个元素。
  4. observe 方法做的事情就是,如果该元素为简单的字符串或者数字则不做任何处理,直接return;若该元素为对象的话则调用 new Observer(value) 方法去处理该元素,就返回到最先类继续往下走; 从第4步就能发现为什么通过索引改动数组的元素无法触发视图更新了
  5. 回到 Observer,如果判断需要被监听的值不为数组,则调用walk方法,处理该元素
  6. walk 方法中调用Object.keys()方法来遍历对象,并且调用 defineReactive(obj, keys[i])方法
  7. defineReactive 方法做的事情就是利用Object.defineProperty()方法去监听对象中的每个属性;
  8. 在 set 方法中会去调用dep.notify()方法,该方法就是去通知watcher触发update方法去重新渲染视图;
  9. 在get方法中会将该属性添加到相关的依赖中

源码

// 由于浏览器兼容问题,Object.observe 方法不能起到监听数据变动,所以vue在实现的过程中自己有封装了 Observe 类

  1. Observer类的 constructor 方法中对需要被监听的值进行了判断
  2. 如果该值为数组,那么需要调用 observeArray 方法去处理
  3. observeArray方法中主要是遍历数组中每个元素,并且调用observe方法去处理每个元素。
  4. 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
    }
}

复制代码

通过上述例子可以发现:

  1. 对于一个单一简单的数组,如果需要更改里面元素的值时,需要通过this.$set方法进行更改,此时可以被监听到,并且触发视图更新
  2. 需要强调的一点,如果简单的数组不是通过this.$set方法更改的那么不管watch中是否设置deep:true都没有用,无法监听到数组发生的变化
  3. 通过例二可以发现,对象数组中,每个对象中的元素可以直接进行更改并且能够触发视图更新,但是如果需要通过watch来监听这个数组是否发生变化,则必须加上deep:true
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
经验分享 程序员 微信小程序 职场和发展