Vue.js 核心源码解析

114 阅读7分钟

Vue2.x 响应式

手写迷你版

  • 导出 Vue 的构造函数,并实现 __init 方法
// 导出 Vue 的构造函数
export function Vue(options = {}) {
  this.__init(options);
}

// initMixin
Vue.prototype.__init = function (options) {
  this.$options = options;

  // 假设这里就是一个 el,已经 querySelector 的
  this.$el = options.el;
  this.$data = options.data;
  this.$methods = options.methods;
};
  • 代理操作 proxy
Vue.prototype.__init = function (options) {
  this.$options = options;

  // 假设这里就是一个 el,已经 querySelector 的
  this.$el = options.el;
  this.$data = options.data;
  this.$methods = options.methods;

  // beforeCreate -- initState -- initData
  // 代理操作:把 this.$data.message 代理到 this.message
  proxy(this, this.$data);
};

// this.$data.message --> this.message 代理操作
function proxy(target, data) {
  // target: this
  // data: 数据对象
  Object.keys(data).forEach(key => {
    Object.defineProperty(target, key, {
      enumerable: true, // 可枚举
      configurable: true, // 可配置
      get() {
        return data[key];
      },
      set(newVal) {
        // 考虑 NaN 的情况
        if (!isSameVal(data[key], newVal)) {
          data[key] = newVal;
        }
      },
    });
  });
}

function isSameVal(a, b) {
  return a === b || (Number.isNaN(a) && Number.isNaN(b));
}
  • 观察者 observer
Vue.prototype.__init = function (options) {
  this.$options = options;

  // 假设这里就是一个 el,已经 querySelector 的
  this.$el = options.el;
  this.$data = options.data;
  this.$methods = options.methods;

  // beforeCreate -- initState -- initData
  // 代理操作:把 this.$data.message 代理到 this.message
  proxy(this, this.$data);

  // observer() - Object.defineProperty
  // 依赖收集
  observer(this.$data);
};

function observer(data) {
  new Observer(data);
}

class Observer {
  constructor(data) {
    // 这里只考虑对象的情况
    this.walk(data);
  }

  walk(data) {
    if (data && typeof data === 'object') {
      Object.keys(data).forEach(key => this.defineReactive(data, key, data[key]));
    }
  }

  // 把每一个 data 里面的数据收集起来
  defineReactive(obj, key, value) {
    let that = this;

    // 如果 value 还是对象,递归处理
    this.walk(value);

    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get() {
        return value;
      },
      set(newVal) {
        if (!isSameVal(value, newVal)) {
          value = newVal;

          // 赋值进来的新值是没有响应式的,所以要再 walk 一次,给到响应式
          that.walk(newVal);
        }
      },
    });
  }
}

function isSameVal(a, b) {
  return a === b || (Number.isNaN(a) && Number.isNaN(b));
}
  • 视图怎么更新? => 数据改变,视图才会更新,需要去观察
  • 观察者(订阅者)Watcher
// 视图怎么更新?
// 数据改变,视图才会更新,需要去观察
// 1、new Watcher(vm, 'num', () => { 更新视图上的 num 显示 })
class Watcher {
  constructor(vm, key, cb) {
    this.vm = vm; // vm 就是 Vue 的实例
    this.key = key;
    this.cb = cb;

    // 2、此时 Dep.target 作为一个全局变量理解,放的就是这个 watcher
    Dep.target = this;

    // 3、一旦进行了这一句赋值,是不是就触发了这个值的 getter 函数
    this.__old = vm[key];

    // 把 Dep.target 删除
    Dep.target = null;
  }

  // 8、执行所有的 cb 函数
  update() {
    const newVal = this.vm[this.key];
    if (!isSameVal(newVal, this.__old)) {
      this.cb(newVal);
    }
  }
}
  • 目标(发布者)Dep
// 每一个数据都要有一个 dep 的依赖
class Dep {
  constructor() {
    this.watchers = new Set(); // 定义一个存放所有 watcher 的集合数组
  }

  // 添加 watcher 方法
  add(watcher) {
    if (watcher && watcher.update) {
      this.watchers.add(watcher);
    }
  }

  // 7、让所有的 watcher 执行 update 方法
  // 通知方法
  notify() {
    this.watchers.forEach(watcher => watcher.update());
  }
}
  • Observer 类中,在 getter 中触发依赖收集,在 setter 中通知更新
class Observer {
  constructor(data) {
    // 这里只考虑对象的情况
    this.walk(data);
  }

  walk(data) {
    if (data && typeof data === 'object') {
      Object.keys(data).forEach(key => this.defineReactive(data, key, data[key]));
    }
  }

  // 把每一个 data 里面的数据收集起来
  defineReactive(obj, key, value) {
    let that = this;

    // 如果 value 还是对象,递归处理
    this.walk(value);

    const dep = new Dep();

    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get() {
        // 4、对于 num 来说,要执行这一句
        // 5、num 中的 dep,就有了这个 watcher
        Dep.target && dep.add(Dep.target);

        return value;
      },
      set(newVal) {
        if (!isSameVal(value, newVal)) {
          value = newVal;

          // 赋值进来的新值是没有响应式的,所以要再 walk 一次,给到响应式
          that.walk(newVal);

          // 6、重新 set 值,通知更新
          dep.notify();
        }
      },
    });
  }
}
  • 编译模板 Compiler
Vue.prototype.__init = function (options) {
  this.$options = options;

  // 假设这里就是一个 el,已经 querySelector 的
  this.$el = options.el;
  this.$data = options.data;
  this.$methods = options.methods;

  // beforeCreate -- initState -- initData
  // 代理操作:把 this.$data.message 代理到 this.message
  proxy(this, this.$data);

  // observer() - Object.defineProperty
  // 观察者
  observer(this.$data);

  // 编译模板
  new Compiler(this);
};

// 编译模板
class Compiler {
  constructor(vm) {
    this.vm = vm;
    this.el = vm.$el;
    this.methods = vm.$methods;

    this.compile(vm.$el);
  }

  // 这里是递归编译 #app 下面的所有的节点内容
  compile(el) {
    // 类数组
    let childNodes = el.childNodes;

    Array.from(childNodes).forEach(node => {
      if (node.nodeType === 3) {
        // 文本节点 <p>{{ name }}</p>
        this.compileText(node);
      } else if (node.nodeType === 1) {
        // 元素节点 <input type="text" v-model="message" />
        this.compileElement(node);
      }

      // 如果还有子节点,递归遍历
      if (node.childNodes && node.childNodes.length) {
        this.compile(node);
      }
    });
  }

  // 处理文本节点
  compileText(node) {
    // 匹配出来 {{message}}
    let reg = /\{\{(.+?)\}\}/;
    let value = node.textContent;
    if (reg.test(value)) {
      let key = RegExp.$1.trim();

      // 首次开始赋值
      node.textContent = value.replace(reg, this.vm[key]);

      // 添加观察者
      new Watcher(this.vm, key, val => {
        // 数据改变时的更新
        node.textContent = val;
      });
    }
  }

  // 处理元素节点
  compileElement(node) {
    // 简化,只做匹配 v-on 和 v-model 的匹配
    // v-on:click='' / v-model=''
    if (node.attributes.length) {
      Array.from(node.attributes).forEach(attr => {
        let attrName = attr.name;
        if (attrName.startsWith('v-')) {
          // v- 指令匹配成功,可能是 v-on:click 或者 v-model
          attrName = attrName.indexOf(':') > -1 ? attrName.substring(5) : attrName.substring(2);

          let key = attr.value;
          // 调用更新函数
          this.update(node, key, attrName, this.vm[key]);
        }
      });
    }
  }

  // 更新函数
  update(node, key, attrName, value) {
    if (attrName === 'model') {
      node.value = value;

      new Watcher(this.vm, key, val => (node.value = val));

      node.addEventListener('input', () => {
        this.vm[key] = node.value;
      });
    } else if (attrName === 'click') {
      node.addEventListener(attrName, this.methods[key].bind(this.vm));

      // node.addEventListener(attrName, () => {
      //   this.methods[key].call(this.vm);
      // });
    }
  }
}

迷你版完整代码

  • ./index.html
<!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>Vue 2.x</title>
    <script type="module">
      // ! 在 script 标签上加上 type=module 属性来改变方式,可以使用 import
      // import { Vue } from './index.js';
      import { Vue } from './vue2.js';

      let vm = new Vue({
        el: document.querySelector('#app'),
        data: {
          message: 'hello luyi',
          num: 35,
        },
        methods: {
          increase() {
            this.num++;
          },
        },
      });
    </script>
  </head>
  <body>
    <div id="app">
      <h1>{{ message }}</h1>
      <h2>{{ num }}</h2>

      <input type="text" v-model="message" />
      <input type="text" v-model="num" />

      <button v-on:click="increase">【+】</button>
    </div>
  </body>
</html>
  • ./vue2.js
// 导出 Vue 的构造函数
export function Vue(options = {}) {
  this.__init(options);
}

// initMixin
Vue.prototype.__init = function (options) {
  this.$options = options;

  // 假设这里就是一个 el,已经 querySelector 的
  this.$el = options.el;
  this.$data = options.data;
  this.$methods = options.methods;

  // beforeCreate -- initState -- initData
  // 代理操作:把 this.$data.message 代理到 this.message
  proxy(this, this.$data);

  // observer() - Object.defineProperty
  // 观察者
  observer(this.$data);

  // 编译模板
  new Compiler(this);
};

// this.$data.message --> this.message 代理操作
function proxy(target, data) {
  // target: this
  // data: 数据对象
  Object.keys(data).forEach(key => {
    Object.defineProperty(target, key, {
      enumerable: true, // 可枚举
      configurable: true, // 可配置
      get() {
        return data[key];
      },
      set(newVal) {
        // 考虑 NaN 的情况
        if (!isSameVal(data[key], newVal)) {
          data[key] = newVal;
        }
      },
    });
  });
}

function observer(data) {
  new Observer(data);
}

class Observer {
  constructor(data) {
    // 这里只考虑对象的情况
    this.walk(data);
  }

  walk(data) {
    if (data && typeof data === 'object') {
      Object.keys(data).forEach(key => this.defineReactive(data, key, data[key]));
    }
  }

  // 把每一个 data 里面的数据收集起来
  defineReactive(obj, key, value) {
    let that = this;

    // 如果 value 还是对象,递归处理
    this.walk(value);

    const dep = new Dep();

    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get() {
        // 4、对于 num 来说,要执行这一句
        // 5、num 中的 dep,就有了这个 watcher
        Dep.target && dep.add(Dep.target);

        return value;
      },
      set(newVal) {
        if (!isSameVal(value, newVal)) {
          value = newVal;

          // 赋值进来的新值是没有响应式的,所以要再 walk 一次,给到响应式
          that.walk(newVal);

          // 6、重新 set 值,通知更新
          dep.notify();
        }
      },
    });
  }
}

// 视图怎么更新?
// 数据改变,视图才会更新,需要去观察
// 1、new Watcher(vm, 'num', () => { 更新视图上的 num 显示 })
class Watcher {
  constructor(vm, key, cb) {
    this.vm = vm; // vm 就是 Vue 的实例
    this.key = key;
    this.cb = cb;

    // 2、此时 Dep.target 作为一个全局变量理解,放的就是这个 watcher
    Dep.target = this;

    // 3、一旦进行了这一句赋值,是不是就触发了这个值的 getter 函数
    this.__old = vm[key];

    // 把 Dep.target 删除
    Dep.target = null;
  }

  // 8、执行所有的 cb 函数
  update() {
    const newVal = this.vm[this.key];
    if (!isSameVal(newVal, this.__old)) {
      this.cb(newVal);
    }
  }
}

// 每一个数据都要有一个 dep 的依赖
class Dep {
  constructor() {
    this.watchers = new Set(); // 定义一个存放所有 watcher 的集合数组
  }

  // 添加 watcher 方法
  add(watcher) {
    if (watcher && watcher.update) {
      this.watchers.add(watcher);
    }
  }

  // 7、让所有的 watcher 执行 update 方法
  // 通知方法
  notify() {
    this.watchers.forEach(watcher => watcher.update());
  }
}

// 编译模板
class Compiler {
  constructor(vm) {
    this.vm = vm;
    this.el = vm.$el;
    this.methods = vm.$methods;

    this.compile(vm.$el);
  }

  // 这里是递归编译 #app 下面的所有的节点内容
  compile(el) {
    // 类数组
    let childNodes = el.childNodes;

    Array.from(childNodes).forEach(node => {
      if (node.nodeType === 3) {
        // 文本节点 <p>{{ name }}</p>
        this.compileText(node);
      } else if (node.nodeType === 1) {
        // 元素节点 <input type="text" v-model="message" />
        this.compileElement(node);
      }

      // 如果还有子节点,递归遍历
      if (node.childNodes && node.childNodes.length) {
        this.compile(node);
      }
    });
  }

  // 处理文本节点
  compileText(node) {
    // 匹配出来 {{message}}
    let reg = /\{\{(.+?)\}\}/;
    let value = node.textContent;
    if (reg.test(value)) {
      let key = RegExp.$1.trim();

      // 首次开始赋值
      node.textContent = value.replace(reg, this.vm[key]);

      // 添加观察者
      new Watcher(this.vm, key, val => {
        // 数据改变时的更新
        node.textContent = val;
      });
    }
  }

  // 处理元素节点
  compileElement(node) {
    // 简化,只做匹配 v-on 和 v-model 的匹配
    // v-on:click='' / v-model=''
    if (node.attributes.length) {
      Array.from(node.attributes).forEach(attr => {
        let attrName = attr.name;
        if (attrName.startsWith('v-')) {
          // v- 指令匹配成功,可能是 v-on:click 或者 v-model
          attrName = attrName.indexOf(':') > -1 ? attrName.substring(5) : attrName.substring(2);

          let key = attr.value;
          // 调用更新函数
          this.update(node, key, attrName, this.vm[key]);
        }
      });
    }
  }

  // 更新函数
  update(node, key, attrName, value) {
    if (attrName === 'model') {
      node.value = value;

      new Watcher(this.vm, key, val => (node.value = val));

      node.addEventListener('input', () => {
        this.vm[key] = node.value;
      });
    } else if (attrName === 'click') {
      node.addEventListener(attrName, this.methods[key].bind(this.vm));

      // node.addEventListener(attrName, () => {
      //   this.methods[key].call(this.vm);
      // });
    }
  }
}

function isSameVal(a, b) {
  return a === b || (Number.isNaN(a) && Number.isNaN(b));
}

看一看源码/列大纲

Vue2.x 和 Vue3.x 的对比

  • Vue 3.x 中使用了 Proxy 作为响应式,天生的代理,不用考虑属性重写、数组这些 2.xhack 的情况;
  • diff 增加了 最大递增子序列 的算法,使移动节点,更高效;
  • 架构采用 monorepo 的方式,分层清晰,同时把编译部分也进行了一些拆解;
  • vue3 对编译的内容进行了重写,template -- render 函数;
    • vue2 编译基于正则, vue3 编译基于状态机; --> 【状态机和 ast 编译原理有关】
    • patchFlag 标记哪些元素包含哪些属性;
    • 静态提升;
  • vue3 使用 blockTree,对比需要改变的,优化性能,如果要用 jsx 的写法,就不会优化,但是可以自己去标记;
  • ts 重构;
  • compiler 拆成了四个包,方便去重写;
  • vue2 options API vs vue3 composition API
  • vue3 使用了 rollup 打包,支持 Tree Shaking
  • 实例化方式也有区别;

Vue3.x 响应式

Vue 是怎样追踪变化的?

当我们在 template 模板中使用响应式变量,或者在计算属性中传入 getter 函数后当计算属性中的源数据发生变化后,Vue 总能即时的通知更新并重新渲染组件,其中是通过副作用(effect)函数来实现的。

Vue 通过一个副作用(effect)函数来跟踪当前正在运行的函数,副作用是一个函数包裹器,在函数被调用前就启动跟踪,而 Vue 在派发更新时就能准确的找到这些被收集起来的副作用函数,当数据发生更新时再次执行它。

手写迷你版

  • 实现 reactive
/* 
  vue2 中响应式数据,只要在 data 中定义就可以了
  vue3 的响应式数据,需要使用 reactive/ref

  reactive(obj) => 参数是一个对象
  const reactiveData = reactive({ message: 'hello', num: 0 });
*/
export function reactive(data) {
  // 不是对象,直接返回
  if (!isObject(data)) return;

  // vue2 中响应式:Object.defineProperty
  // vue3 中响应式:Proxy 代理
  return new Proxy(data, {
    get(target, key, receiver) {
      // receiver 指向原始的读操作所在的那个对象, 一般情况下,就是 Proxy 的实例
      // Reflect.get 方法查找并返回 target 对象的 name 属性,如果没有该属性,则返回 undefined
      const ret = Reflect.get(target, key, receiver);

      // 收集依赖 => vue2 的 dep.add(Dep.target);
      track(target, key);

      return isObject(ret) ? reactive(ret) : ret;
    },
    set(target, key, value, receiver) {
      Reflect.set(target, key, value, receiver);

      // 触发 => vue2 的 dep.notify()
      trigger(target, key);

      return true;
    },
    deleteProperty(target, key) {
      const ret = Reflect.defineProperty(target, key);
      trigger(target, key);
      return ret;
    },
    has(target, key) {
      track(target, key);
      return Reflect.has(target, key);
    },
    ownKeys(target) {
      track(target, key);
      return Reflect.ownKeys(target);
    },
  });
}

function isObject(data) {
  return data && typeof data === 'object';
}
  • 实现 ref
/* 
  定义:const num = ref(0);
  使用:num.value,ref 会自动包裹一层 value
*/
export function ref(init) {
  // init 就是传入的数据
  class RefImpl {
    constructor(init) {
      // ! 8、执行 ref => 设置 getter、setter 方法
      this.__value = init;
    }

    get value() {
      // ! 11、模板上使用值的时候,触发 getter
      track(this, 'value');

      return this.__value;
    }

    set value(newVal) {
      // ! 14、再次赋值时触发 setter
      this.__value = newVal;

      trigger(this, 'value');
    }
  }

  return new RefImpl(init);
}
  • 定义全局变量
let targetMap = new WeakMap();

// Vue 2.x 的时候,是不是有一个 ”全局变量“,叫做 Dep.target -- watcher
// Vue 3.x 还要有这么一个全局变量,来存放这么个东西 -- effect 副作用
let activeEffect;
  • 实现 track
// 进行依赖的收集
function track(target, key) {
  // 首先获取 target,如果没有进行赋值
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()));
  }

  // 再判断 depsMap 中有没有 key,没有就赋值
  let dep = depsMap.get(key);
  if (!dep) {
    depsMap.set(key, (dep = new Set()));
  }

  /* 
    最后 targetMap 的数据结构是这样:

    targetMap: { // WeakMap()
      target: { // Map()
        key: [ReactiveEffect, ReactiveEffect, ...] // Set()
      }
    }
  */

  trackEffect(dep);
}

function trackEffect(dep) {
  // 相当于 Vue 2.x 中的 Dep.target && dep.add(Dep.target)
  if (!dep.has(activeEffect)) {
    // ! 13、dep.add(activeEffect)
    dep.add(activeEffect);
  }
}
  • 实现 trigger
// 触发
function trigger(target, key) {
  // ! 15、触发 trigger => 获取 depsMap 对象
  const depsMap = targetMap.get(target);
  if (!depsMap) return;

  // ! 16、遍历执行 activeEffect 的 run 方法
  // effect 上面肯定有一个 run 方法
  depsMap.get(key).forEach(effect => effect && effect.run());
}
  • 定义一个 effect 的函数
// 在定义一个 effect 的函数中,第一个参数是一个函数
// 如果这个函数中有使用 ref/reactive,当值改变的时候 effect 就会执行
/* 
  effect(() => {
    console.log(num.value)
  })

  effect 函数会执行,__effect.run() => activeEffect = this; => 
  执行 this.fn() 会触发的当前值的 getter => track 会收集 activeEffect
*/
// ! 2、执行 effect 方法
function effect(fn, options = {}) {
  // ! 3、new ReactiveEffect 实例
  let __effect = new ReactiveEffect(fn);

  // 如果 options 中有 lazy 属性,在加载的时候不会先执行,需要手动调用才会执行
  if (!options.lazy) {
    // ! 4、执行 run 方法
    __effect.run();
  }

  return __effect;
}

class ReactiveEffect {
  constructor(fn) {
    this.fn = fn;
  }

  run() {
    // ! 5、activeEffect 保存
    activeEffect = this;
    return this.fn();
  }
}
  • 实现 computed
/* 
  const m = computed(() => `${num.value}!!!`)
  computed 执行的时候返回 __computed,当获取 m.value 的时候,触发 getter,执行 e.run();
*/
export function computed(fn, options = {}) {
  // 只考虑函数的情况
  let __computed;
  const e = effect(fn, { lazy: true });
  __computed = {
    get value() {
      // 手动执行 run 方法
      return e.run();
    },
  };

  return __computed;
}
  • 实现 mount
// ! 1、mount 执行
export function mount(instance, el) {
  /* 
    instance => App
    el => 挂载的 id
  */
  effect(function () {
    // ! 6、执行 effect 方法的 fn
    instance.$data && update(instance, el);
  });

  // ! 7、执行 setup 方法,$data 设置值
  instance.$data = instance.setup();

  // ! 9、执行 update 方法
  update(instance, el);

  function update(instance, el) {
    // ! 10、el.innerHTML 首次渲染
    el.innerHTML = instance.render();
  }
}

迷你版完整代码

  • ./index.html
<!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>
    <script type="module">
      import { mount, ref, reactive, computed } from './vue3.js';
      const App = {
        $data: null,
        setup() {
          let count = ref(0);
          let time = reactive({ seconds: 0 });
          let com = computed(() => `get computed ${count.value + time.seconds}`);

          setInterval(() => {
            count.value++;
            // 值改变触发 trigger => activeEffect => run()
          }, 1000);

          setInterval(() => {
            time.seconds++;
          }, 1000);

          return {
            count,
            time,
            com,
          };
        },
        render() {
          return `
            <h1>Reactive Works</h1>
            <h5>this is ref data: ${this.$data.count.value}</h5>
            <h5>this is reactive data: ${this.$data.time.seconds}</h5>
            <h5>this is computed data: ${this.$data.com.value}</h5>
          `;
        },
      };

      mount(App, document.querySelector('#app'));
    </script>
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>
  • ./vue3.js
let targetMap = new WeakMap();

// Vue 2.x 的时候,是不是有一个 ”全局变量“,叫做 Dep.target -- watcher
// Vue 3.x 还要有这么一个全局变量,来存放这么个东西 -- effect 副作用
let activeEffect;

/* 
  vue2 中响应式数据,只要在 data 中定义就可以了
  vue3 的响应式数据,需要使用 reactive/ref

  reactive(obj) => 参数是一个对象
  const reactiveData = reactive({ message: 'hello', num: 0 });
*/
export function reactive(data) {
  // 不是对象,直接返回
  if (!isObject(data)) return;

  // vue2 中响应式:Object.defineProperty
  // vue3 中响应式:Proxy 代理
  return new Proxy(data, {
    get(target, key, receiver) {
      // receiver 指向原始的读操作所在的那个对象, 一般情况下,就是 Proxy 的实例
      // Reflect.get 方法查找并返回 target 对象的 name 属性,如果没有该属性,则返回 undefined
      const ret = Reflect.get(target, key, receiver);

      // 收集依赖 => vue2 的 dep.add(Dep.target);
      track(target, key);

      return isObject(ret) ? reactive(ret) : ret;
    },
    set(target, key, value, receiver) {
      Reflect.set(target, key, value, receiver);

      // 触发 => vue2 的 dep.notify()
      trigger(target, key);

      return true;
    },
    deleteProperty(target, key) {
      const ret = Reflect.defineProperty(target, key);
      trigger(target, key);
      return ret;
    },
    has(target, key) {
      track(target, key);
      return Reflect.has(target, key);
    },
    ownKeys(target) {
      track(target, key);
      return Reflect.ownKeys(target);
    },
  });
}

/* 
  定义:const num = ref(0);
  使用:num.value,ref 会自动包裹一层 value
*/
export function ref(init) {
  // init 就是传入的数据
  class RefImpl {
    constructor(init) {
      // ! 8、执行 ref => 设置 getter、setter 方法
      this.__value = init;
    }

    get value() {
      // ! 11、模板上使用值的时候,触发 getter
      track(this, 'value');

      return this.__value;
    }

    set value(newVal) {
      // ! 14、再次赋值时触发 setter
      this.__value = newVal;

      trigger(this, 'value');
    }
  }

  return new RefImpl(init);
}

/* 
  const m = computed(() => `${num.value}!!!`)
  computed 执行的时候返回 __computed,当获取 m.value 的时候,触发 getter,执行 e.run();
*/
export function computed(fn, options = {}) {
  // 只考虑函数的情况
  let __computed;
  const e = effect(fn, { lazy: true });
  __computed = {
    get value() {
      // 手动执行 run 方法
      return e.run();
    },
  };

  return __computed;
}

// ! 1、mount 执行
export function mount(instance, el) {
  /* 
    instance => App
    el => 挂载的 id
  */
  effect(function () {
    // ! 6、执行 effect 方法的 fn
    instance.$data && update(instance, el);
  });

  // ! 7、执行 setup 方法,$data 设置值
  instance.$data = instance.setup();

  // ! 9、执行 update 方法
  update(instance, el);

  function update(instance, el) {
    // ! 10、el.innerHTML 首次渲染
    el.innerHTML = instance.render();
  }
}

// 在定义一个 effect 的函数中,第一个参数是一个函数
// 如果这个函数中有使用 ref/reactive,当值改变的时候 effect 就会执行
/* 
  effect(() => {
    console.log(num.value)
  })

  effect 函数会执行,__effect.run() => activeEffect = this; => 
  执行 this.fn() 会触发的当前值的 getter => track 会收集 activeEffect
*/
// ! 2、执行 effect 方法
function effect(fn, options = {}) {
  // ! 3、new ReactiveEffect 实例
  let __effect = new ReactiveEffect(fn);

  // 如果 options 中有 lazy 属性,在加载的时候不会先执行,需要手动调用才会执行
  if (!options.lazy) {
    // ! 4、执行 run 方法
    __effect.run();
  }

  return __effect;
}

class ReactiveEffect {
  constructor(fn) {
    this.fn = fn;
  }

  run() {
    // ! 5、activeEffect 保存
    activeEffect = this;
    return this.fn();
  }
}

// 进行依赖的收集
function track(target, key) {
  // 首先获取 target,如果没有进行赋值
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()));
  }

  // 再判断 depsMap 中有没有 key,没有就赋值
  let dep = depsMap.get(key);
  if (!dep) {
    depsMap.set(key, (dep = new Set()));
  }

  /* 
    最后 targetMap 的数据结构是这样:

    targetMap: { // WeakMap()
      target: { // Map()
        key: [ReactiveEffect, ReactiveEffect, ...] // Set()
      }
    }
  */

  trackEffect(dep);
}

function trackEffect(dep) {
  // 相当于 Vue 2.x 中的 Dep.target && dep.add(Dep.target)
  if (!dep.has(activeEffect)) {
    // ! 13、dep.add(activeEffect)
    dep.add(activeEffect);
  }
}

// 触发
function trigger(target, key) {
  // ! 15、触发 trigger => 获取 depsMap 对象
  const depsMap = targetMap.get(target);
  if (!depsMap) return;

  // ! 16、遍历执行 activeEffect 的 run 方法
  // effect 上面肯定有一个 run 方法
  depsMap.get(key).forEach(effect => effect && effect.run());
}

function isObject(data) {
  return data && typeof data === 'object';
}

/* 
  mount 执行 => 
  执行 effect 方法 => new ReactiveEffect => 执行 run 方法 => activeEffect 保存 => 执行 effect 方法的 fn =>
  执行 setup 方法,$data 设置值 => 执行 ref => 设置 getter、setter 方法 =>
  执行 update 方法 => el.innerHTML 首次渲染
  
  模板上使用值的时候,触发 getter =>
  触发 getter => track 依赖收集 => 设置数据结构 => dep.add(activeEffect) => getter 返回值渲染模板

  再次赋值时触发 setter => 触发 trigger => 获取 depsMap 对象 => 遍历执行 activeEffect 的 run 方法(也就是上面 dep.add 添加的) =>
  run 方法中的 fn 就是首次执行 effect 方法的 fn => 执行 update 方法 => el.innerHTML 更新
*/

步骤描述

let count = ref(0);
effect(() => {console.log(count.value)});

// effect 的函数函会执行 run();
// 触发 count.value 去收集依赖,也就是 track;
// track 的过程 => targetMap() 添加副作用函数

setInterval(() => {
  count.value++;
}, 1000);

// 触发 trigger => targetMap 取出对应的 activeEffect 对象,执行 run 方法;

看一看源码/列大纲

Vue3 的 diff

  • 路径:core/packages/runtime-core/src/renderer.ts

问题 2:Vue2 中如何实现数据响应

通过 Object.DefineProperty,劫持数据的 getset,结合观察者模式实现。

问题 3:请写一个观察者模式

class Subject {
  deps: Array<Observer>;
  state: Number;
  constructor() {
    this.deps = [];
    this.state = 0;
  }

  attach(obs: Observer) {
    this.deps.push(obs);
  }
  setState(num: Number) {
    this.state = num;
    this.notifyAllObservers();
  }

  notifyAllObservers(): void {
    this.deps.forEach(obs => {
      obs.run(this.state);
    });
  }
}

abstract class Observer {
  subject: Subject;
  constructor(subject: Subject) {
    this.subject = subject;
    this.subject.attach(this);
  }

  abstract run(data: String | Number | undefined): void;
}
class BinaryObserver extends Observer {
  constructor(subject: Subject) {
    super(subject);
  }

  run(data: String | Number | undefined): void {
    console.log('hello, this is binaryObserver:' + data);
  }
}

class ArrayObserver extends Observer {
  constructor(subject: Subject) {
    super(subject);
  }

  run(data: String | Number | undefined): void {
    console.log('hello, this is ArrayObserver:' + data);
  }
}

// main
const subject = new Subject();
const obs = new BinaryObserver(subject);
subject.setState(10);
subject.setState(15);

问题 4:请写一个最长上升子序列的算法

function maxUp(arr) {
  if (arr.length === 0) return 0;
  let dp = [];
  let max = 0;
  dp[0] = 1;
  for (let i = 1; i < arr.length; i++) {
    dp[i] = 1;
    for (let j = 0; j < i; j++) {
      if (arr[i] > arr[j]) {
        dp[i] = Math.max(dp[j] + 1, dp[i]);
      }
    }
    max = Math.max(dp[i], max);
  }
  return max;
}

发布订阅模式 vs 观察者模式

观察者模式

所谓观察者模式,其实就是为了实现 松耦合(loosely coupled)

用《Head First 设计模式》里的气象站为例子,每当气象测量数据有更新,changed() 方法就会被调用,于是我们可以在 changed() 方法里面,更新气象仪器上的数据,比如温度、气压等等。

但是这样写有个问题,就是如果以后我们想在 changed() 方法被调用时,更新更多的信息,比如说湿度,那就要去修改 changed() 方法的代码,这就是紧耦合的坏处。

怎么解决呢?使用观察者模式,面向接口编程,实现松耦合。

观察者模式里面,changed() 方法所在的实例对象,就是被观察者(Subject,或者叫 Observable),它只需维护一套观察者(Observer)的集合,这些 Observer 实现相同的接口,Subject 只需要知道,通知 Observer 时,需要调用哪个统一方法就好了:

image-20221009212852606.png

发布订阅模式

在发布订阅模式里,发布者,并不会直接通知订阅者,换句话说,发布者和订阅者,彼此互不相识。

互不相识?那他们之间如何交流?

答案是,通过第三者,也就是在消息队列里面,我们常说的经纪人Broker

image-20220929132945662.png

发布者只需告诉 Broker,我要发的消息,topicAAA

订阅者只需告诉 Broker,我要订阅 topicAAA 的消息;

于是,当 Broker 收到发布者发过来消息,并且 topicAAA 时,就会把消息推送给订阅了 topicAAA 的订阅者。当然也有可能是订阅者自己过来拉取,看具体实现。

也就是说,发布订阅模式里,发布者和订阅者,不是松耦合,而是完全解耦的。

image-20221009213028967.png

总结

从表面上看:

  • 观察者模式里,只有两个角色 —— 观察者 + 被观察者;
  • 而发布订阅模式里,却不仅仅只有发布者和订阅者两个角色,还有一个经常被我们忽略的 —— 经纪人 Broker

往更深层次讲:

  • 观察者和被观察者,是松耦合的关系;
  • 发布者和订阅者,则完全不存在耦合;

从使用层面上讲:

  • 观察者模式,多用于单个应用内部;
  • 发布订阅模式,则更多的是一种跨应用的模式(cross-application pattern),比如我们常用的消息中间件;