還記得,我們在將vue響應式原理的時候說過,Object.defineProperty()這個方法對對象的屬性方法的添加或者刪除不能做到實時的監聽,數組通過索引去 修改數組都是不能被檢測?所以vue實現了set方法,那么實現的set方法的原理是什么呢?
vm.$set( target, propertyName/index, value )
參數:
{Object | Array} target
{string | number} propertyName/index
{any} value
用法:
向響應式對象中添加一個屬性,並確保這個新屬性同樣是響應式的,且觸發視圖更新。它必須用於向響應式對象上添加新屬性,因為 Vue
無法探測普通的新增屬性。
-
注意:對象不能是
Vue
實例,或者Vue
實例的根數據對象。
源碼: src/core/observer/index.js
1 export function set (target, key, val){ 2 if (process.env.NODE_ENV !== 'production' && 3 (isUndef(target) || isPrimitive(target)) 4 ) { 5 warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`) 6 } 7 if (Array.isArray(target) && isValidArrayIndex(key)) { 8 target.length = Math.max(target.length, key) 9 target.splice(key, 1, val) 10 return val 11 } 12 if (key in target && !(key in Object.prototype)) { 13 target[key] = val 14 return val 15 } 16 const ob = (target: any).__ob__ 17 if (target._isVue || (ob && ob.vmCount)) { 18 process.env.NODE_ENV !== 'production' && warn( 19 'Avoid adding reactive properties to a Vue instance or its root $data ' + 20 'at runtime - declare it upfront in the data option.' 21 ) 22 return val 23 } 24 if (!ob) { 25 target[key] = val 26 return val 27 } 28 defineReactive(ob.value, key, val) 29 ob.dep.notify() 30 return val 31 }
首先,判斷在非生產環境,傳入的target如果是undefined
、null
或是原始類型,則直接跑出錯誤。
其次,如果判斷target如果是個數組,並且key是索引的話,那么就取當前數組長度與key
這兩者的最大值作為數組的新長度,然后使用數組的splice
方法將傳入的索引key
對應的val
值添加進數組。Array
類型數據的變化偵測方式時說過,數組的splice
方法已經被我們創建的攔截器重寫了,也就是說,當使用splice
方法向數組內添加元素時,該元素會自動被變成響應式的。
接下來,如果傳入的target不是數組,那就當作是對象來處理。首先判斷傳入的key
是否已經存在於target
中,如果存在,表明這次操作不是新增屬性,而是對已有的屬性進行簡單的修改值,那么就只修改屬性值即可,接下來獲取到traget
的__ob__
屬性,我們說過,該屬性是否為true
標志着target
是否為響應式對象,接着判斷如果tragte
是 Vue
實例,或者是 Vue
實例的根數據對象,則拋出警告並退出程序,接着判斷如果ob
屬性為false
,那么表明target
不是一個響應式對象,那么我們只需簡單給它添加上新的屬性,不用將新屬性轉化成響應式,最后,如果target
是對象,並且是響應式,那么就調用defineReactive
方法將新屬性值添加到target
上,defineReactive
方會將新屬性添加完之后並將其轉化成響應式,最后通知依賴更新。
總的來說set的執行流程應該是這樣的:
受 ES5 的限制,Vue.js 不能檢測到對象屬性的添加或刪除。因為 Vue.js 在初始化實例時將屬性轉為 getter/setter,所以屬性必須在 data 對象上才能讓 Vue.js 轉換它,才能讓它是響應的。要處理這種情況,我們可以使用$set()方法,既可以新增屬性,又可以觸發視圖更新。
舉例
給student對象新增age屬性
data () {
return {
student: {
name: '',
sex: ''
}
}
}
mounted () {
this.student.age = 24
}
可以發現可以新增屬性,但是不會觸發更新視圖
當我們使用
mounted () {
this.$set(this.student,"age", 24)
}
發現可以更新視圖
舉例:
<template>
<div class="">
<p>a:
結果:
1
2
分析原因:
vue不能檢測到對象屬性的添加或刪除。所以屬性必須在data對象上才能讓vue轉換它,讓它響應。所以當我需要觀測到這個對象的變化,目標對象沒有該屬性的時候,需要使用 $set。如果遇到對象賦值,視圖不更新,第一種在對象里面加上我們想要渲染的屬性,然后直接用this.obj.a 來修改,第二種用vue.set的方法,this.$set(this.obj, 'b', 2)來給對象加新屬性和賦值