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包含一組觀察數組的變異方法,因此它們也將會觸發視圖更新,這些方法以下: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
bash

vm.items.splice(newLength)
複製代碼

小發現:經過下標直接更改數組元素,沒法觸發渲染機制更新視圖,但此時數組的值已經發生變化,若同時有其餘數據更改致使從新渲染時,綁定數組的dom也會更新顯示最新的數據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';
複製代碼

此時的更改是能夠被監聽到,而且觸發視圖更新的ui

個人疑問:爲何vue不對單一的數組元素進行數據劫持呢,親測能夠經過數據劫持的方式來觸發set方法this

// 個人測試方式以下
------------- 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類spa

  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 類
3d

  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
相關文章
相關標籤/搜索