Vue.set($set)
Vue.delete($delete)
我們發現 $set
和 $delete
定義在 stateMixin
函式中,如下程式碼:
export function stateMixin (Vue: Class<Component>) {
// flow somehow has problems with directly declared definition object
// when using Object.defineProperty, so we have to procedurally build up
// the object here.
const dataDef = {}
dataDef.get = function () { return this._data }
const propsDef = {}
propsDef.get = function () { return this._props }
if (process.env.NODE_ENV !== 'production') {
dataDef.set = function () {
warn(
'Avoid replacing instance root $data. ' +
'Use nested data properties instead.', this )
}
propsDef.set = function () {
warn(`$props is readonly.`, this)
}
}
Object.defineProperty(Vue.prototype, '$data', dataDef)
Object.defineProperty(Vue.prototype, '$props', propsDef)
Vue.prototype.$set = set
Vue.prototype.$delete = del
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object ): Function {
const vm: Component = this
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) {
try {
cb.call(vm, watcher.value)
} catch (error) {
handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
}
}
return function unwatchFn () {
watcher.teardown()
}
}}複製程式碼
是不是太長,太複雜?看不懂?
不急我們慢慢往下看,逐步介紹:
上面定義常量和環境判斷就不說了直接看核心:
Vue.prototype.$set = set
Vue.prototype.$delete = del複製程式碼
可以看到 $set
和 $delete
的值分別是是 set
和 del
其實我們發現initGlobalAPI
函式中定義了:
Vue.set = set
Vue.delete = del複製程式碼
不難看出其實 Vue.set == $set ,Vue.delete == $delete
接下來看看Vue.set程式碼:
export function set (target: Array<any> | Object, key: any, val: any): any {
if (process.env.NODE_ENV !== 'production' && (isUndef(target) || isPrimitive(target)) ) {
warn(`Cannot set reactive property on undefined,
null, or primitive value: ${(target: any)}`)
}
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
target.splice(key, 1, val) return val
}
if (key in target && !(key in Object.prototype)) {
target[key] = val return val
}
const ob = (target: any).__ob__
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.' )
return val }
if (!ob) {
target[key] = val
return val
}
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}複製程式碼
set
函式接收三個引數:第一個引數 target
是將要被新增屬性的物件,第二個引數 key
以及第三個引數 val
分別是要新增屬性的鍵名和值。
if判斷中isUndef
函式用來判斷一個值是否是 undefined
或 null
函式用來判斷一個值是否是原始型別值isPrimitive
(ECMAScript 有 5 種原始型別(primitive type),即 Undefined、Null、Boolean、Number 和 String)
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
target.splice(key, 1, val)
return val
}複製程式碼
這個判斷主要是對target與key做了校驗判斷是否是個陣列和key是否為有效的陣列索引
target.length = Math.max(target.length, key)
target.splice(key, 1, val)複製程式碼
這就涉及到上篇部落格講的(陣列變異處理)
target.length = Math.max(target.length, key)複製程式碼
將陣列的長度修改為 target.length
和 key
中的較大者,否則如果當要設定的元素的索引大於陣列長度時 splice
無效。
target.splice(key, 1, val)複製程式碼
陣列的 splice
變異方法能夠完成陣列元素的刪除、新增、替換等操作。而 target.splice(key, 1, val)
就利用了替換元素的能力,將指定位置元素的值替換為新值,同時由於 splice
方法本身是能夠觸發響應的
然後接下來一個if:
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}複製程式碼
如果 target
不是一個陣列,那麼必然就是純物件了,當給一個純物件設定屬性的時候,假設該屬性已經在物件上有定義了,那麼只需要直接設定該屬性的值即可,這將自動觸發響應,因為已存在的屬性是響應式的
key in target複製程式碼
判斷key
在 target
物件上,或在 target
的原型鏈上
!(key in Object.prototype)複製程式碼
同時必須不能在 Object.prototype
const ob = (target: any).__ob__
複製程式碼
定義了 ob
常量,它是資料物件 __ob__
屬性的引用
defineReactive(ob.value, key, val) ob.dep.notify()複製程式碼
defineReactive
函式設定屬性值,這是為了保證新新增的屬性是響應式的。
__ob__.dep.notify()
從而觸發響應。這就是新增全新屬性觸發響應的原理
if (!ob) { target[key] = val return val }複製程式碼
target
也許原本就是非響應的,這個時候 target.__ob__
是不存在的,所以當發現 target.__ob__
不存在時,就簡單的賦值即可
if (target._isVue || (ob && ob.vmCount)) {
複製程式碼
Vue
例項物件擁有 _isVue
屬性,所以當第一個條件成立時,那麼說明你正在使用 Vue.set/$set
函式為 Vue
例項物件新增屬性,為了避免屬性覆蓋的情況出現,Vue.set/$set
函式不允許這麼做,在非生產環境下會列印警告資訊
(ob && ob.vmCount)複製程式碼
這個就涉及比較深:主要是觀測一個資料物件是否為根資料物件,所以所謂的根資料物件就是 data
物件
當使用 Vue.set/$set
函式為根資料物件新增屬性時,是不被允許的。
因為這樣做是永遠觸發不了依賴的。原因就是根資料物件的 Observer
例項收集不到依賴(觀察者)
set講完了 講講delete
Vue.delete/$delete
還是一樣先看原始碼:
export function del (target: Array<any> | Object, key: any) {
if (process.env.NODE_ENV !== 'production' &&(isUndef(target) || isPrimitive(target)) ) {
warn(`Cannot delete reactive property on undefined,
null, or primitive value: ${(target: any)}`)
}
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.splice(key, 1) return
}
const ob = (target: any).__ob__
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid deleting properties on a Vue instance or its root $data ' +
'- just set it to null.' )
return
}
if (!hasOwn(target, key)) {
return
}
delete target[key]
if (!ob) {
return
}
ob.dep.notify()
}
複製程式碼
del
函式接收兩個引數,分別是將要被刪除屬性的目標物件 target
以及要刪除屬性的鍵名 key
第一個if判斷和set一樣 就不講了
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.splice(key, 1)
return
}複製程式碼
第二個判斷其實和set也差不多。。。刪除陣列索引(同樣是變異陣列方法,觸發響應)
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid deleting properties on a Vue instance or its root $data ' +
'- just set it to null.' )
return
}複製程式碼
其實也不用說了 判斷都一樣(不能刪除Vue
例項物件或根資料的屬性)
if (!hasOwn(target, key)) { return }複製程式碼
檢測key
是否是 target
物件自身擁有的屬性
if (!ob) { return }複製程式碼
判斷ob物件是否存在如果不存在說明 target
物件原本就不是響應的,所以直接返回(return
)即可
如果 ob
物件存在,說明 target
物件是響應的,需要觸發響應才行,即執行 ob.dep.notify()
。
進行觀測和依賴收集。