前言
- 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复制代码