基本认识
watch和computed的区别:
1、computed计算属性,关注点在模版,主要是抽离和复用模版中复杂的数据逻辑。
特点:当函数内部的依赖发生变化的后,数据才会重新计算。
2、watch侦听器,关注点在数据(data或computed中的数据)更新,主要负责给数据增加监听,当数据更新的时候监听器函数就会执行。
特点:数据更新的时候,需要完成什么样的逻辑。
export default {data(){return{result: 0}},watch: {// 可以获取到数据更新的新值和旧值result(newVal, oldVal) {console.log(newVal, oldVal);}}};
简单实现
例如我们实现一个简单的watch侦听器,先看一下我们的结构目录:
├─ 03-watch├─ Vue.js├─ index.html├─ main.js├─ reactive.js└─ watcher.js
首先我们先去main.js文件中写出我们大致的结构:
import Vue from "./Vue.js";const vm = new Vue({data() {return {result: 0};},watch: {// 监听 data 中的 resultresult(newVal, oldVal) {console.log("watch result:", newVal, oldVal);}}});console.log(vm);vm.result = 100;vm.result = 200;console.log(vm.result);
接下来我们专注Vue.js文件就可以了,我们采用ES6的类来书写:
class Vue {constructor(options) {// 这里是 Vue 类的入口}init(vm, watch) {// 这里主要处理一些初始化的数据}initData(vm) {// 处理 data() 中的响应式数据}initWatcher(vm, watch) {// 处理 watch 中的监听数据}}export default Vue;
以上就是我们今天Vue文件的大概结构,下面我们先来处理data中的数据:
import { reactive } from "./reactive.js";class Vue {constructor(options) {// 我们在实例 Vue 的时候,传递进来一个对象,所以我们可以进行解构const { data, watch } = options;// 因为 data 中的数据是可以直接被访问到的,就像这样 vm.result// 所以我们需要把 data 中的数据挂在到 Vue 实例上面this.$data = data();this.init(this, watch);}init(vm, watch) {// 调用初始化 datathis.initData(vm);}initData(vm) {// 我们把处理 data 这部分的代码,抽离出去为一个 reactive.js 文件// reactive 方法介绍 3 个参数:当前实例、当 get data 中数据的回调、当 set data 中数据的回调reactive(vm, (key, value) => {}, (key, newVal, oldVal) => {});}}export default Vue;
处理data中的数据:
export function reactive(vm, __get__, __set__) {const _data = vm.$data;// 遍历 data 中的数据,也就是 { result: 0 }for (const key in _data) {// 添加拦截Object.defineProperty(vm, key, {get() {// 当获取 vm.result 的时候就会执行回调,且返回数据__get__(key, _data[key]);return _data[key];},set(newVal) {// 当给 vm.result 设置值的时候就会执行回调,且赋值const oldVal = _data[key];_data[key] = newVal;__set__(key, newVal, oldVal);}});}}
到这里我们已经处理好data的数据了,实例数据如下:
根据reactive.js的set方法,我们这样打算:既然到result改变的时候,我们可以拦截到,且执行了回调方法,那我们在回调了去执行watch不就可以了吗?
那么我们就继续来写watcher.js文件:
class Watcher {constructor() {this.watchers = [];/*** watchers 的数据结构:* {* key: result* fn: result(newVal, oldVal)* }** 通过 addWatcher(vm, watcher, key) 去往 watchers 里面添加数据* */}// 往 watchers 里面添加数据addWatcher(vm, watcher, key) {this._addWatchProp({key,fn: watcher[key].bind(vm)});}invoke(key, newVal, oldVal) {// 用 addWatcher 保存的值去遍历对比this.watchers.map((item) => {if (item.key === key) {// 调用 result() 传入新值、旧值item.fn(newVal, oldVal);}});}_addWatchProp(watchProp) {this.watchers.push(watchProp);}}export default Watcher;
接着我们在Vue.js文件中引入watcher.js:
import { reactive } from "./reactive.js";import Watcher from "./watcher.js";class Vue {constructor(options) {// 我们在实例 Vue 的时候,传递进来一个对象,所以我们可以进行解构const { data, watch } = options;// 因为 data 中的数据是可以直接被访问到的,就像这样 vm.result// 所以我们需要把 data 中的数据挂在到 Vue 实例上面this.$data = data();this.init(this, watch);}init(vm, watch) {// 调用初始化 datathis.initData(vm);// 初始化 watch,且传入实例对象和 watch 对象(也就是 { watch: { result:xxx }})// 然后我们就能得到一个 watcher 的实例,并把它保存到实例对象上面,方面后面直接调用执行const watcherIns = this.initWatcher(vm, watch);this.$watch = watcherIns.invoke.bind(watcherIns);}initData(vm) {// 我们把处理 data 这部分的代码,抽离出去为一个 reactive.js 文件// reactive 方法介绍 3 个参数:当前实例、当 get data 中数据的回调、当 set data 中数据的回调reactive(vm, (key, value) => {}, (key, newVal, oldVal) => {// 当设置 vm.result 的时候我们就去执行 watch 的 invoke 方法this.$watch(key, newVal, oldVal);});}initWatcher(vm, watch) {// 枚举 watch 然后新增监听器// 返回实例,实例有调用 watch 的方法,执行监听器const watcherIns = new Watcher();for (const key in watch) {// 实例、watch 对象、resultwatcherIns.addWatcher(vm, watch, key);}return watcherIns;}}export default Vue;

这样就算完成了,当我们去改变vm.result的时候,watch中的result()方法就会被执行。
