乐趣区

Vue原理依赖更新-源码版

写文章不容易,点个赞呗兄弟
专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧
研究基于 Vue 版本 【2.5.17】

如果你觉得排版难看,请点击 下面链接 或者 拉到 下面 关注公众号 也可以吧

【Vue 原理】依赖更新 – 源码版

如果对依赖收集完全没有概念的同学,可以先看我这篇白话版

响应式原理 – 白话版

我们已经讲过了 依赖收集

【Vue 原理】依赖收集 – 源码版之基本数据类型

【Vue 原理】依赖收集 – 源码版之引用数据类型

现在就要看依赖更新了哈哈哈,毕竟收集完是要更新的嘛

其实依赖更新挺简单的,就是两步

修改属性值
通知保存的依赖进行更新
重点只需要看 Object.defineProperty 设置的 set 函数,当给数据重赋新值的时候,自然会触发 set 函数,完成依赖更新

function defineReactive(obj, key, val) {var dep = new Dep(); 
    var childOb = observe(val);   

    Object.defineProperty(obj, key, {get(){   
            ... 属性被读取,完成依赖收集            
            // 返回闭包值
            return val
       },

       set(newVal) {    
       
            // 值没有变化
           if (newVal ===val) return
           
           // 修改闭包值
           val = newVal;    
       
            // 如果属性已经存在过,设置新值的时候,会重新调用一遍
           childOb = observe(newVal);  
         
            // 触发更新
           dep.notify();}    
    }); 
}

依赖更新重点就重在 通知更新

而通知更新的重点,只有一句话,【dep.notify】

所以,我们重点去了解这句话,如何通知,如何更新

好的,dep 在第一篇讲过了

【Vue 原理】依赖收集 – 源码版之基本数据类型

我们知道,dep 主要是存储依赖的,再看一遍源码

var Dep = function Dep() {this.subs = []; // 依赖存储器

};

// 遍历 subs,逐个通知依赖,就是逐个调用 watcher.update
Dep.prototype.notify = function() {var subs = this.subs.slice();    

    for (var i = 0, l = subs.length; i < l; i++) {subs[i].update();}
};

看过了源码,我们知道了,原来通知更新是【遍历依赖存储器】,然后一个个【调用 watcher.update】

因为 subs 装的是 watcher,所以,subs[0].update 就是 watcher.update

于是问题又来了,watcher.update 是怎么就更新了???

function Watcher(vm, expOrFn) {    

    this.vm = vm;    

    // 保存传入的更新函数    
    this.getter = expOrFn;

    // 新建 watcher 的时候,立即执行更新函数
    this.get();};



Watcher.prototype.get = function() {  

    // 执行更新函数
    this.getter.call(this.vm,this.vm);  

};

Watcher.prototype.update = function() {this.get()
}

看到上面的源码

1Watcher 新建实例的时候,会保存传入的函数(这个函数会作为更新用)

2watcher 实例有 update 方法,作用是执行上一步保存的更新函数

那么 watcher 是什么时候开始创建的呢?

以页面 watcher 举例,探索整个实例构建的基本流程

function Vue(options) {this._init(options);

}

Vue.prototype._init = function(options) {
    // ... 处理组件选项等
    this.$mount()}

Vue.prototype.$mount = function() {

    // ... 解析 template 成 redner 函数保存

    /** 每个实例新建一个 watcher,并且利用 watcher 保存更新函数 **/

    new Watcher(this,        

        // 这个函数是更新函数,传入 watcher 保存下来,用于后面页面初始化或者页面更新

        function() {
             /** ... 调用保存的渲染函数生成 VNode,并生成 DOM 插入页面中 **/
        }
    );
};

看上面的源码 和注释大概就可以很清楚了

从【new Vue】到【vm._init】初始化 到【vm.$mount】挂载到页面,整个流程就完整了

重点是清楚 watcher 的更新函数

更新函数

我们可以看到这个页面的更新函数,作用是调用 渲染函数,然后生成 DOM 节点插入页面中。

更新函数会传入 Watcher,然后被保存到 watcher 的实例中

“整个函数涉及的源码很多,但是这里一律而过”

所以,通知更新做了这些工作

1、直接调用 watcher.update,也就是重新调用给 watcher 保存的更新函数

2、更新更新函数就是执行渲染函数,然后读取实例最新的值(已被修改过的值),最后重新生成 DOM 节点

3、DOM 节点 插入或替换页面,完成更新

画个通知流程图

退出移动版