基本认识

watchcomputed的区别:
1、computed计算属性,关注点在模版,主要是抽离和复用模版中复杂的数据逻辑。
特点:当函数内部的依赖发生变化的后,数据才会重新计算。
2、watch侦听器,关注点在数据(datacomputed中的数据)更新,主要负责给数据增加监听,当数据更新的时候监听器函数就会执行。
特点:数据更新的时候,需要完成什么样的逻辑。

  1. export default {
  2. data(){
  3. return{
  4. result: 0
  5. }
  6. },
  7. watch: {
  8. // 可以获取到数据更新的新值和旧值
  9. result(newVal, oldVal) {
  10. console.log(newVal, oldVal);
  11. }
  12. }
  13. };

简单实现

例如我们实现一个简单的watch侦听器,先看一下我们的结构目录:

  1. ├─ 03-watch
  2. ├─ Vue.js
  3. ├─ index.html
  4. ├─ main.js
  5. ├─ reactive.js
  6. └─ watcher.js

首先我们先去main.js文件中写出我们大致的结构:

  1. import Vue from "./Vue.js";
  2. const vm = new Vue({
  3. data() {
  4. return {
  5. result: 0
  6. };
  7. },
  8. watch: {
  9. // 监听 data 中的 result
  10. result(newVal, oldVal) {
  11. console.log("watch result:", newVal, oldVal);
  12. }
  13. }
  14. });
  15. console.log(vm);
  16. vm.result = 100;
  17. vm.result = 200;
  18. console.log(vm.result);

接下来我们专注Vue.js文件就可以了,我们采用ES6的类来书写:

  1. class Vue {
  2. constructor(options) {
  3. // 这里是 Vue 类的入口
  4. }
  5. init(vm, watch) {
  6. // 这里主要处理一些初始化的数据
  7. }
  8. initData(vm) {
  9. // 处理 data() 中的响应式数据
  10. }
  11. initWatcher(vm, watch) {
  12. // 处理 watch 中的监听数据
  13. }
  14. }
  15. export default Vue;

以上就是我们今天Vue文件的大概结构,下面我们先来处理data中的数据:

  1. import { reactive } from "./reactive.js";
  2. class Vue {
  3. constructor(options) {
  4. // 我们在实例 Vue 的时候,传递进来一个对象,所以我们可以进行解构
  5. const { data, watch } = options;
  6. // 因为 data 中的数据是可以直接被访问到的,就像这样 vm.result
  7. // 所以我们需要把 data 中的数据挂在到 Vue 实例上面
  8. this.$data = data();
  9. this.init(this, watch);
  10. }
  11. init(vm, watch) {
  12. // 调用初始化 data
  13. this.initData(vm);
  14. }
  15. initData(vm) {
  16. // 我们把处理 data 这部分的代码,抽离出去为一个 reactive.js 文件
  17. // reactive 方法介绍 3 个参数:当前实例、当 get data 中数据的回调、当 set data 中数据的回调
  18. reactive(vm, (key, value) => {}, (key, newVal, oldVal) => {});
  19. }
  20. }
  21. export default Vue;

处理data中的数据:

  1. export function reactive(vm, __get__, __set__) {
  2. const _data = vm.$data;
  3. // 遍历 data 中的数据,也就是 { result: 0 }
  4. for (const key in _data) {
  5. // 添加拦截
  6. Object.defineProperty(vm, key, {
  7. get() {
  8. // 当获取 vm.result 的时候就会执行回调,且返回数据
  9. __get__(key, _data[key]);
  10. return _data[key];
  11. },
  12. set(newVal) {
  13. // 当给 vm.result 设置值的时候就会执行回调,且赋值
  14. const oldVal = _data[key];
  15. _data[key] = newVal;
  16. __set__(key, newVal, oldVal);
  17. }
  18. });
  19. }
  20. }

到这里我们已经处理好data的数据了,实例数据如下:
image.png

根据reactive.jsset方法,我们这样打算:既然到result改变的时候,我们可以拦截到,且执行了回调方法,那我们在回调了去执行watch不就可以了吗?

那么我们就继续来写watcher.js文件:

  1. class Watcher {
  2. constructor() {
  3. this.watchers = [];
  4. /**
  5. * watchers 的数据结构:
  6. * {
  7. * key: result
  8. * fn: result(newVal, oldVal)
  9. * }
  10. *
  11. * 通过 addWatcher(vm, watcher, key) 去往 watchers 里面添加数据
  12. * */
  13. }
  14. // 往 watchers 里面添加数据
  15. addWatcher(vm, watcher, key) {
  16. this._addWatchProp({
  17. key,
  18. fn: watcher[key].bind(vm)
  19. });
  20. }
  21. invoke(key, newVal, oldVal) {
  22. // 用 addWatcher 保存的值去遍历对比
  23. this.watchers.map((item) => {
  24. if (item.key === key) {
  25. // 调用 result() 传入新值、旧值
  26. item.fn(newVal, oldVal);
  27. }
  28. });
  29. }
  30. _addWatchProp(watchProp) {
  31. this.watchers.push(watchProp);
  32. }
  33. }
  34. export default Watcher;

接着我们在Vue.js文件中引入watcher.js

  1. import { reactive } from "./reactive.js";
  2. import Watcher from "./watcher.js";
  3. class Vue {
  4. constructor(options) {
  5. // 我们在实例 Vue 的时候,传递进来一个对象,所以我们可以进行解构
  6. const { data, watch } = options;
  7. // 因为 data 中的数据是可以直接被访问到的,就像这样 vm.result
  8. // 所以我们需要把 data 中的数据挂在到 Vue 实例上面
  9. this.$data = data();
  10. this.init(this, watch);
  11. }
  12. init(vm, watch) {
  13. // 调用初始化 data
  14. this.initData(vm);
  15. // 初始化 watch,且传入实例对象和 watch 对象(也就是 { watch: { result:xxx }})
  16. // 然后我们就能得到一个 watcher 的实例,并把它保存到实例对象上面,方面后面直接调用执行
  17. const watcherIns = this.initWatcher(vm, watch);
  18. this.$watch = watcherIns.invoke.bind(watcherIns);
  19. }
  20. initData(vm) {
  21. // 我们把处理 data 这部分的代码,抽离出去为一个 reactive.js 文件
  22. // reactive 方法介绍 3 个参数:当前实例、当 get data 中数据的回调、当 set data 中数据的回调
  23. reactive(vm, (key, value) => {}, (key, newVal, oldVal) => {
  24. // 当设置 vm.result 的时候我们就去执行 watch 的 invoke 方法
  25. this.$watch(key, newVal, oldVal);
  26. });
  27. }
  28. initWatcher(vm, watch) {
  29. // 枚举 watch 然后新增监听器
  30. // 返回实例,实例有调用 watch 的方法,执行监听器
  31. const watcherIns = new Watcher();
  32. for (const key in watch) {
  33. // 实例、watch 对象、result
  34. watcherIns.addWatcher(vm, watch, key);
  35. }
  36. return watcherIns;
  37. }
  38. }
  39. export default Vue;

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

源码地址:
https://github.com/xiechen1201/JSPlusPlus/blob/main/%E8%85%BE%E8%AE%AF%E8%AF%BE%E5%A0%82/Vue%E6%9C%AC%E5%B0%8A03/03/main.js