前言
- watch 中每一個屬性都會new一個用戶watcher(new Watcher)
- 在數據初始化得時候 開始new Watcher, Dep.target 指向此時的用戶watcher, 此時該屬性中的加入用戶watcherdep.addSub.push(watcher)
- 當data中的數據發生變化時, 調用該數據的全部watcher
- Watcher先將老值存起來 數據發生變化時 將新值與老值 返回給表達式(cb)
html 和 javascript 模板
<div id="app">{{ name }}</div><script src="dist/vue.js"></script><script>var vm = new Vue({data: {name: 'one'},/** 用戶watcher 幾種方式 */watch: {// name(newVal, oldVal) {// console.log(newVal, oldVal)// },// name: [// function(newVal, oldVal) {// console.log(newVal, oldVal)// },// function(a, b) {// console.log(a, b)// }// ],// 'obj.n'(newVal, oldVal) {// console.log(newVal, oldVal)// }}
})
vm.$mount('#app')
vm.$watch('name', function(newValue, oldValue) {console.log(newValue, oldValue)
})setTimeout(() => {
vm.name = 'two'}, 2000)複製代碼

正題
$watch
export function stateMixin(Vue) {
Vue.prototype.$watch = function(key, handler, options={}) {// 用戶建立的watcheroptions.user = truenew Watcher(this, key, handler, options)
}
}複製代碼
options.watch 初始化開始執行
export function initState(vm) {const opts = vm.$optionsif (opts.watch) {
initWatch(vm, opts.watch)
}
}複製代碼
/** initWatche module *//**
* @description 調用$watch
*/function createWatcher(vm, key, handler) {return vm.$watch(key, handler)
}/**
* @description 初始化 watch 將觀察屬性的function拆分出來
* @description 每一個觀察屬性 建立new watcher
*/function initWatch(vm, watch) {for (const key in watch) {const handler = watch[key]if (Array.isArray(handler)) {for (let i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i])
}
} else {
createWatcher(vm, key, handler)
}
}
}複製代碼
Watcher 類 (關鍵)
import { pushTarget, popTarget } from './dep'import { queueWatcher } from './scheduler'/** 給new Watcher一個id */let id = 0/**
* @description 數據劫持時 指望一個屬性對應多個watcher 同時一個watcher對應多個屬性
*/class Watcher {constructor(vm, exprOrFn, cb, options) {this.vm = vmthis.exprOrFn = exprOrFnthis.cb = cbthis.options = options this.id = id++ this.deps = []this.depsId = new Set() /** 用戶watcher */this.user = !!options.user// 看這裏 將獲取的函數名 封裝成表達式 if (typeof exprOrFn == 'string') {this.getter = function() {// vm取值 'obj.n' -> ['obj', 'n'] -> vm['obj']['n']let path = exprOrFn.split('.')let obj = vmfor (let i = 0; i < path.length; i++) {
obj = obj[path[i]]
}return obj
}
} else {this.getter = exprOrFn
}/** 用戶watcher 默認取得第一次值 */this.value = this.get()
}/** render生成vnode時 dep.push(watcher) 並更新視圖 */get() {
pushTarget(this)// 看這裏 在VM上尋找劫持的數據 可將該屬性上push用戶Watcherconst value = this.getter.call(this.vm)
popTarget()return value
}update() {
queueWatcher(this)
}run() {/** 考慮1 渲染組件watcher *//** 考慮2 用戶watcher(newValue oldValue) */let newValue = this.get()let oldValue = this.valuethis.value = newValue/** 看這裏 用戶watcher 將值返回給cb, 並調用 */if (this.user) {this.cb.call(this.vm, newValue, oldValue)
}
}/** 存儲dep 並排除生成vnode時屢次調用同樣屬性 只存一個 dep 或 watcher *//** 其次當屬性發生變化時將再也不存儲dep 和 watcher */addDep(dep) {let id = dep.idif (!this.depsId.has(id)) {this.depsId.add(id)this.deps.push(dep)
dep.addSub(this)
}
}
}export default Watcher複製代碼
Dep 類
/** 每一個劫持的屬性 加上惟一的標識 */let id = 0/**
* @description 每一個劫持的屬性 new Dep
*/class Dep {constructor() {this.id = id++this.subs = []
}/** dep傳給watcher */depend() {if (Dep.target) {
Dep.target.addDep(this)
}
}addSub(watcher) {this.subs.push(watcher)
}notify() {this.subs.forEach(watcher => watcher.update())
}
}
Dep.target = nulllet stack = []export function pushTarget(watcher) {
Dep.target = watcher
stack.push(watcher)
}export function popTarget() {
stack.pop()
Dep.target = stack[stack.length - 1]
}export default Dep複製代碼