Vue2核心原理(简易版)-watch功能实现
watch是用来做什么的?
Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。
watch是什么?
一个对象,键是需要观察的表达式,值是对应回调函数。值也可以是方法名,或者包含选项的对象。Vue 实例将会在实例化时调用 $watch(),遍历 watch 对象的每一个 property。文档传送 示例:
// 定义watch
let vm = new Vue({
el: '#app',
data() {
return {
name: 'vue2'
}
},
watch: {
name(newVal, oldVal) {
console.log(`watch name change form ${oldVal} to ${newVal}`);
},
}
})
// 改变name
setTimeout(() => {
vm.name = 'vue3'
}, 1000)
// 控制台打印
// watch name change form vue2 to vue3
怎么做这个watch?
- 首先我们要回顾vue依赖收集的原理和实现,请看我的前一篇文章,依赖收集。
核心原理就是对传入Vue options的watch对象里面的所有的属性,都添加一个自定义watcher,收集对应属性的依赖,然后当这个依赖项更新的时候,不仅通知它的渲染watcher(可能没有),也会通知当前的这个自定义watcher,从而叫到你定义的callback,完成函数内的逻辑。
- 怎么拿到更新时的newValue和oldValue?
首先,watcher绑定的时候,默认要执行一遍this.get方法,这样我们就完成了渲染watcher一样的行为,那就是'touch'到这个属性,让依赖项感知到了自己被依赖,添加订阅者,并且此时的值就是初始值,我们把它记录到watcher上,
this.value = this.get();。然后呢,每一次watcher更新调用update方法,我们都会重新调用this.get,那么此时的返回值是不是就是最新值了呢?再加上我们先前记录的this.value,很棒,我们有了newValue和oldValue了!
- 代码具体实现
a. 初始化watch对象b. 修改Watcher类,应对自定义watcher属性的情况// 添加$watch mixin export function stateMixin(Vue) { Vue.prototype.$watch = function (key, handler, options = {}) { const vm = this; options.user = true; new Watcher(vm, key, handler, options); }; } // 对传入的watch添加自定义watcher function initWatch() { const vm = this; let watchs = vm.$options.watch; for (let key in watchs) { let handler = watchs[key]; if (Array.isArray(handler)) { for (let i = 0; i < handler.length; i++) { createWatcher(vm, key, handler[i]); } } else { createWatcher(vm, key, handler); } } } function createWatcher(vm, key, handler) { vm.$watch(key, handler); }// observer/watcher.js constructor(vm, exprOrFn, cb, options) { this.id = "watcher-" + id++; this.vm = vm; this.exprOrFn = exprOrFn; this.cb = cb; this.deps = []; this.depsId = new Set(); this.options = options; this.user = !!options.user; if (typeof exprOrFn === "string") { this.getter = function () { let path = exprOrFn.split("."); let obj = path.reduce((pre, currentPath) => { return pre[currentPath]; }, vm); return obj; }; } else { this.getter = exprOrFn; } this.value = this.get(); } get() { pushTarget(this); // 记录老的值 const value = this.getter.call(this.vm); popTarget(this); // 返回老的值 return value; } update() { // vue中的更新操作是异步的 // 多次调用update 我希望先将watcher缓存下来,等一会一起更新 queueWatcher(this); } run() { const oldValue = this.value; const newValue = this.get(); if (this.user) { this.cb(newValue, oldValue); // 别忘了把新的值记录为下次的老值 this.value = newValue; } }
完整代码
github.com/Sotyoyo/do-… 分支do-watch
原文链接
如果你在非掘金地址看到这篇文章,这里有原文传送门,您的点赞是对我最大的支持!
完 🎉
下一讲,computed计算属性实现