用法

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
</head>

<body>
<div id="app">
</div>
<script src="vue.js"></script>
<!--<script src="<https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.14/vue.min.js>" integrity="sha512-XdUZ5nrNkVySQBnnM5vzDqHai823Spoq1W3pJoQwomQja+o4Nw0Ew1ppxo5bhF2vMug6sfibhKWcNJsG8Vj9tg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>-->
<script>
    const vm = new Vue({
        el: '#app',
        data: {
            firstName: 'John',
            lastName: 'Doe',
        },
        watch: {
            // 第一种写法: 直接写一个函数
            firstName: function (newValue, oldValue) { // 观察者
                console.log(newValue, oldValue, '函数写法');
            },
            // 第二种写法:数组写法
            // firstName: [
            //     (newValue, oldValue) => {
            //         console.log(newValue, oldValue);
            //     },
            //     (newValue, oldValue) => {
            //         console.log(newValue, oldValue);
            //     }
            // ]
        }
    });
    // 第三种写法 vm.$watch
    vm.$watch(() => vm.firstName, (newValue, oldValue) => {
        console.log(newValue, oldValue, 'vm.$watch + 箭头函数');
    });
    vm.$watch('firstName', (newValue, oldValue) => {
        console.log(newValue, oldValue, 'vm.$watch + 字符串');
    });
    // 底层最终都调用的 $watch 的写法

    // 依赖变化,重新计算
    setTimeout(() => {
        vm.firstName = 'Larry';
    }, 1000);
</script>
</body>
</html>

实现

watch 本质就是实例化一个个 watcher 去监听属性的变化,然后执行用户的回调。

所以 watch 底层也是 watcher

import { observe } from "./observe/index";
import Watcher from "./observe/watcher";
import {watch} from "rollup";
import Dep from "./observe/dep";

export function initState(vm) {
  const opts = vm.$options; // 获取所有的选项
  if (opts.data) {
    initData(vm);
  }
  if (opts.computed) {
    initComputed(vm);
  }
  if (opts.watch) {
    initWatch(vm);
  }
}

function initWatch(vm) {
  let watch = vm.$options.watch;

  for (let key in watch) {
    const handler = watch[key]; // 字符串 数组 函数
    if (Array.isArray(handler)) {
      for (let i = 0, len = handler.length; i < len; i++) {
        createWatcher(vm, key, handler[i]);
      }
    } else {
      createWatcher(vm, key, handler);
    }
  }
}

...
import { initMixin } from "./init";
import { initLifeCycle } from "./lifecycle";
import Watcher, { nextTick } from "./observe/watcher";
import { initGlobalAPI } from "./globalAPI";

function Vue(options) {
  this._init(options); // 调用 init 方法
}

// vue 源码不是这么挂载的:
Vue.prototype.$nextTick = nextTick;

initMixin(Vue); // 采用函数的形式,传入 Vue 构造函数,来扩展 Vue.prototype._init 方法
initLifeCycle(Vue);
initGlobalAPI(Vue);

/**
 * 底层 $watch
 * @param exprOrFn 表达式或者函数
 * @param cb 回调
 */
Vue.prototype.$watch = function(exprOrFn, cb) {
  // firstname // 可以把字符串形式也处理成函数
  // () => vm.firstName

  // firstName 的值变化了, 直接执行 cb 函数即可
  new Watcher(this, exprOrFn, { user: true }, cb); // 标识用户自己写的 watcher
}

export default Vue;

Watcher 进行改造:

import Dep, {popTarget, pushTarget} from "./dep";

let id = 0;

class Watcher {
  // 不同组件有不同的watcher   目前只有一个 渲染根实例的
  constructor(vm, exprOrFn, options, cb) {
    this.id = id++;
    this.renderWatcher = options; // options = true 表明 watcher 是一个渲染 watcher

    if (typeof exprOrFn === 'string') {
      this.getter = function() {
        return vm[exprOrFn]; // vm.xxxMethod
      }
    } else {
      this.getter = exprOrFn; // getter意味着调用这个函数可以发生取值操作
    }

		this.user = options.user; // 标识是否是用户自己的 watcher

		// 第一次执行时的老值
    this.value = this.lazy ? undefined : this.get();
    ...
  }
  ...
  run() {
    let oldValue = this.value;
    let newValue = this.get(); // 渲染的时候用的是最新的vm来渲染的
    if (this.user) {
      this.cb.call(this.vm, newValue, oldValue);
    }
  }
}

...
export default Watcher;