咱們發現 $set
和 $delete
定義在 stateMixin
函數中,以下代碼:
react
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()
}
}}複製代碼
是否是太長,太複雜?看不懂?express
不急咱們慢慢往下看,逐步介紹:數組
上面定義常量和環境判斷就不說了直接看核心:bash
Vue.prototype.$set = set
Vue.prototype.$delete = del複製代碼
能夠看到 $set
和 $delete
的值分別是是 set
和 del
函數
其實咱們發現initGlobalAPI
函數中定義了:ui
Vue.set = set
Vue.delete = del複製代碼
不難看出其實 Vue.set == $set ,Vue.delete == $deletethis
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
分別是要添加屬性的鍵名和值。
spa
if判斷中isUndef
函數用來判斷一個值是不是 undefined
或 null
prototype
函數用來判斷一個值是不是原始類型值isPrimitive
code
(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
仍是同樣先看源碼:
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()
。
進行觀測和依賴收集。