我们以MyVue作为类响应式框架,框架的搭建不做赘述。我们模拟Vue源码的实现思路,实例化MyVue时会传递一个选项配置,精简的代码只有一个id挂载元素和一个数据对象data。模拟源码的思路,我们在实例化时会先进行数据的初始化,这一步就是响应式的构建,我们稍后分析。数据初始化后开始进行真实DOM的挂载。

    7.6.2 设置响应式对象 - Observer

    首先引入一个类Observer,这个类的目的是将数据变成响应式对象,利用Object.defineProperty对数据的getter,setter方法进行改写。在数据读取getter阶段我们会进行依赖的收集,在数据的修改setter阶段,我们会进行依赖的更新(这两个概念的介绍放在后面)。因此在数据初始化阶段,我们会利用Observer这个类将数据对象修改为相应式对象,而这是所有流程的基础。

    1. class MyVue {
    2. initData(options) {
    3. if(!options.data) return;
    4. this.data = options.data;
    5. // 将数据重置getter,setter方法
    6. new Observer(options.data);
    7. }
    8. }
    9. // Observer类的定义
    10. class Observer {
    11. constructor(data) {
    12. // 实例化时执行walk方法对每个数据属性重写getter,setter方法
    13. this.walk(data)
    14. }
    15. walk(obj) {
    16. const keys = Object.keys(obj);
    17. for(let i = 0;i< keys.length; i++) {
    18. // Object.defineProperty的处理逻辑
    19. defineReactive(obj, keys[i])
    20. }
    21. }

    那么哪个时间点会实例化watcher并更新数据状态呢?显然在渲染数据到真实DOM时可以创建watcher$mount流程前面章节介绍过,会经历模板生成render函数和render函数渲染真实DOM的过程。我们对代码做了精简,updateView浓缩了这一过程。

    1. class MyVue {
    2. $mount(el) {
    3. // 直接改写innerHTML
    4. const updateView = _ => {
    5. let innerHtml = document.querySelector(el).innerHTML;
    6. let key = innerHtml.match(/{(\w+)}/)[1];
    7. document.querySelector(el).innerHTML = this.options.data[key]
    8. }
    9. // 创建一个渲染的依赖。
    10. new Watcher(updateView, true)
    11. }
    12. }

    7.6.4 依赖管理 - Dep

    watcher如果理解为每个数据需要监听的依赖,那么Dep 可以理解为对依赖的一种管理。数据可以在渲染中使用,也可以在计算属性中使用。相应的每个数据对应的watcher也有很多。而我们在更新数据时,如何通知到数据相关的每一个依赖,这就需要Dep进行通知管理了。并且浏览器同一时间只能更新一个watcher,所以也需要一个属性去记录当前更新的watcher。而Dep这个类只需要做两件事情,将依赖进行收集,派发依赖进行更新。

    1. const dep = new Dep();
    2. const property = Object.getOwnPropertyDescriptor(obj);
    3. let val = obj[key]
    4. if(property && property.configurable === false) return;
    5. Object.defineProperty(obj, key, {
    6. configurable: true,
    7. enumerable: true,
    8. get() {
    9. // 做依赖的收集
    10. if(Dep.target) {
    11. dep.depend()
    12. }
    13. return val
    14. },
    15. set(nval) {
    16. if(nval === val) return
    17. // 派发更新
    18. val = nval
    19. dep.notify();
    20. }
    21. }

    回过头来看watcher,实例化watcher时会将Dep.target设置为当前的watcher,执行完状态更新函数之后,再将Dep.target置空。这样在收集依赖时只要将Dep.target当前的watcher pushDep的数组即可。而在派发更新阶段也只需要重新更新状态即可。

    7.6.6 结果

    一个极简的响应式系统搭建完成。在精简代码的同时,保持了源码设计的思想和逻辑。有了这一步的基础,接下来深入分析源码中每个环节的实现细节会更加简单。