用法: vm.$watch( expOrFn, callback, [options] )
,返回值爲unwatch
是一個函數用來取消觀察;下面主要理解options
中的兩個參數deep
和immediate
以及unwatch
vue
Vue.prototype.$watch = function (expOrFn, cb, options) { const vm = this options = options || {} const watcher = new Watcher(vm, expOrFn, cb, options) if(options.immediate) { cb.call(vm, watcher,.value) } return function unwatchFn() { watcher.teardown() } }
從上面代碼中能夠看出當immediate
爲true
時,就會直接進行執行回調函數數組
dep
收集到watchs
實例對象上,經過this.deps
存起來dep.id
收集到watchs
實例對象上,經過this.depIds
存起來watchs
實例對象的teardown
進行刪除class Watcher { constructor (vm, expOrFn, cb) { this.vm = vm this.deps = [] this.depIds = new Set() if(typeof expOrFn === 'function') { this.getter = expOrFn }else { this.getter = parsePath(expOrFn) } this.cb = cb this.value = this.get() } .... addDep (dep) { const id = dep.id //參數dep是Dep實例對象 if(!this.depIds.has(id)) { //判斷是否存在避免重複添加 this.depIds.add(id) this.deps.push(dep) dep.addSub(this) //this 是依賴 } } teardown () { let i = this.deps.length while (i--) { this.deps[i].removeSub(this) } } } let uid = 0 class Dep { constructor () { this.id = uid++ ... } ... depend () { if(window.target) { window.target.addDep(this) //將this即當前dep對象加入到watcher對象上 } } removeSub (sub) { const index = this.subs.indexOf(sub) if(index > -1) { return this.subs.splice(index, 1) } } }
當執行teardown()
時須要循環;由於例如expOrFn = function () { return this.name + this.age }
,這時會有兩個dep
分別是name
與age
分別都加入了watcher
依賴(this
),都會加入到this.deps
中,因此須要循環將含有依賴的dep
都刪除其依賴函數
deep
幹啥用的,例如data = {arr: [1, 2, {b: 6]}
,當咱們只是監聽data.arr
時,在[1, 2, {b: 66}]
這個數值內部發生變化時,也須要觸發,即b = 888
class Watcher { constructor (vm, expOrFn, cb, options) { this.vm = vm this.deps = [] this.depIds = new Set() if(typeof expOrFn === 'function') { this.getter = expOrFn }else { this.getter = parsePath(expOrFn) } if(options) { //取值 this.deep = !!options.deep }else { this.deep = false } this.cb = cb this.value = this.get() } get () { window.target = this let value = this.getter.call(vm, vm) if(this.deep) { traverse(value) } window.target = undefined return value } ... } const seenObjects = new Set() function traverse (val) { _traverse(val, seenObjects) seenObjects.clear() } function _traverse(val, seen) { let i, keys const isA = Array.isArray(val) if((!isA && isObject(val)) || Object.isFrozen(val)) { //判斷val是不是對象或者數組以及是否被凍結 return } if(val._ob_) { const depId = val._ob_.dep.id //能夠看前面一篇咱們對Observer類添加了this.dep = new Dep(),因此能訪問其dep.id if(seen.has(depId)) { return } seen.add(depId) } if(isA) { i = val.length while (i--) _traverse(val[i], seen) } else { keys = Object.keys(val) i = keys.length while (i--) _traverse(val[i], seen) } }
window.target = this
,寄存依賴let value = this.getter.call(vm, vm)
訪問當前val,並執行get
的dep.depend()
,若是發現val
爲數組,則將依賴加入到observer
的dep
中,也就實現了對當前數組的攔截post
traverse(value)
也就是執行_traverse(val, seenObjects)
;核心就是對被Observer
的val
經過val[i]
經過這種操做,間接觸發get
,將依賴添加到當前數值的dep
中,這樣也就實現了,當內部數據發生變化,也會循環subs
執行依賴的update
,從而觸發回調;當是數組時,只需進行遍歷,看內部是否有Object
對象便可,由於在第二步的時候,會對val
進行判斷是不是數組,變改變七個方法的value,在遍歷;因此這邊只要是內部數組都會進行攔截操做,添加依賴,即對象{}
這種沒沒添加依賴。seenObjects.clear()
當內部因此類型數據都添加好其依賴後,就清空。window.target = undefined
消除依賴用法: vm.$set(target, key, value)
ui
set
則是添加新元素,並須要觸發依賴更新key
值存在,則是修改value
;不存在,則是添加新元素,需新元素要進行響應式處理,以及觸發更新key-value
,無需處理Vue.prototype.$set = function (target, key, val) { if(Array.isArray(target) && isValidArrayIndex(key)) { //是數組而且key有效 target.length = Math.max(target.length, key) //處理key > target.length target.splice(key, 1, val) //添加新元素,並輸出依賴更新同時新元素也會進行`Obsever`處理 return val } if(key in targert && !(key in Object.prototype) { //能遍歷而且是自身key target[key] = val //觸發set,執行依賴更新 return val } const ob = target._ob_ if(target.isVue || (ob && ob.vm.Count) { //不是vue實例也不是vue實例的根對象(即不是this.$data跟對象) //觸發警告 return } if(!ob) { //只添加 target[key] = val return val } defineReactive(ob.value, key, val) //進行響應式處理 ob.dep.notify() //觸發依賴更新 returnv val }
用法: vm.$delete( target, key)
this
delete
則是刪除新元素,並須要觸發依賴更新key
值不存在,直接return
,存在,刪除元素,key-value
,無需其餘處理Vue.prototype.$delete = function (target, key) { if(Array.isArray(target) && isValidArrayIndex(key)) { target.splice(key, 1) return } const ob = target._ob_ if(target.isVue || (ob && ob.vm.Count) { //不是vue實例也不是vue實例的根對象(即不是this.$data跟對象) //觸發警告 return } if(!hasOwn(target, key)) { return } delete target[key] if(!ob) { return } ob.dep.notify() }
掘金地址prototype