index.html 部分

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <input type="text" v-model="v" />
      {{v}}
      <button @click="reset">重置</button>
    </div>
    <script src="./index.js"></script>
    <script>
      const vm = new Mvvm({
        el: "#app",
        data: {
          v: "hello world",
        },
        methods: {
          reset() {
            this.v = "";
          },
        },
      });
    </script>
  </body>
</html>

index.js 部分

class Mvvm {
  constructor(options) {
    const { el, data, methods } = options;
    this.methods = methods;
    this.target = null;
    // 监听this[data的key]所有属性,让变化可追踪
    this.observe(this, data);
    // dom解析:提取{{}}、v-model、事件重写:@click
    this.compile(document.querySelector(el));
  }

  // 数据变化监听
  observe(_this, data) {
    Object.keys(data).forEach((key) => {
      let val = data[key];
      Object.keys(data).forEach((key) => {
        // 如果val是数组或者对象,使用递归实现深层监听,直到val为简单数据类型。从而保证所有属性变化都被监听
        if (typeof val === "object") {
          return this.observe(_this, val);
        }
        // dispatcher用来操作订阅者(watcher) add 或者 update。要配合Object.defineProperty的get和set来使用
        const dispatcher = new Dispatcher();
        Object.defineProperty(_this, key, {
          get: function () {
            console.log("get");
            // this.target会在compile方法中出现,把this.target(一个watcher)添加到dispatcher(将要更新的watcher的列表)中,用于未来更新这个watcher对应的dom
            dispatcher.add(this.target);
            return val;
          },
          set: function (newV) {
            // 值无变化,不处理
            if (newV === val) {
              return;
            }
            console.log(`set`);
            val = newV;
            // 因为set了,值发生变化了,所以要通知get中添加的所有订阅者(watcher):你们要把对应的dom中使用的值更新成newV
            dispatcher.notify(newV);
          },
        });
      });
    });
  }

  // dom解析
  compile(dom) {
    const childs = dom.childNodes;
    for (const node of childs) {
      // nodeType 参考 https://www.w3school.com.cn/jsref/prop_node_nodetype.asp
      if (node.nodeType === 1) {
        const attrs = node.attributes;
        for (const attr of attrs) {
          if (attr.name === "v-model") {
            const name = attr.value;
            // 放到订阅者列表中
            this.target = new Watcher(node, "input");
            // this[name]是为了触发observe的get,才会被监听
            this[name];
            // 由于是demo,假设只有input一种情况,input就会有双向绑定。使用this[name], 并且赋值input的值,来触发observe的get。实现更新信息的发布
            node.addEventListener("input", (e) => {
              this[name] = e.target.value;
            });
          }
          // 使用bind传递this。并代理click事件函数到@click上。这里仅拿click事件实现,实际会有多种事件
          if (attr.name === "@click") {
            const name = attr.value;
            node.addEventListener("click", this.methods[name].bind(this));
          }
        }
      }
      // nodeType 参考 https://www.w3school.com.cn/jsref/prop_node_nodetype.asp
      if (node.nodeType === 3) {
        // 正则匹配{{}}
        const reg = /\{\{(.*)\}\}/;
        const match = node.nodeValue.match(reg);
        if (match) {
          const name = match[1].trim();
          // 放到订阅者列表中
          this.target = new Watcher(node, "text");
          // this[name]是为了触发observe的get,才会被监听
          this[name];
        }
      }
    }
  }
}

// 发布者
class Dispatcher {
  constructor() {
    this.watchers = [];
  }
  // 增加订阅者
  add(watcher) {
    this.watchers.push(watcher);
  }
  // 通知所有订阅者更新
  notify(value) {
    this.watchers.forEach((item) => {
      item.update(value);
    });
  }
}

// 订阅者
class Watcher {
  constructor(node, type) {
    this.node = node;
    this.type = type;
  }
  update(value) {
    // 区别dom类型来赋值
    if (this.type === "input") {
      this.node.value = value;
    }
    if (this.type === "text") {
      this.node.nodeValue = value;
    }
  }
}

解释

vue 在初始化后,执行 Observe 函数把 data 利用 Object.defineProperty 属性监听。同时也会使用 Compile 函数循环 dom,提取 vue 相关的关键字,v-bind 或者 v-model,找到这些值,新建一个 Watcher 实例,然后手动 get 使这些 watch 放入 dep 列表中等待订阅。等待调用 Observerset (input 事件,或者手动赋值),然后通知 dep 中所有 Watcher 调用 update 方法。