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

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

若是你以为排版难看,请点击 下面连接 或者 拉到 下面关注公众号也能够吧闭包

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

上一篇,咱们已经分析过了 基础数据类型的 依赖收集学习

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

这一篇内容是针对 引用数据类型的数据的 依赖收集分析,由于引用类型数据要复杂些,必须分开写prototype

文章很长,高能预警,作好准备耐下心好,确定仍是有点收获的code

可是两个类型的数据的处理,又有不少重复的地方,因此打算只写一些差别性的地方就行了,不然显得废话不少server

两个步骤,都有不一样的地方对象

一、数据初始化blog

二、依赖收集


数据初始化流程

若是数据类型是引用类型,须要对数据进行额外的处理。

处理又分了 对象 和 数组 两种,会分开来说

1对象

一、遍历对象的每一个属性,一样设置响应式,假设属性都是基本类型,处理流程跟上一篇同样

二、每一个数据对象会增长一个 ob 属性

好比设置一个 child 的数据对象

image

下图,你能够看到 child 对象处理以后添加了一个 ob 属性

image

ob_ 属性有什么用啊?

你能够观察到,ob 有一个 dep 属性,这个 dep 是否是有点属性,是的,在上一篇基础数据类型中讲过

那么这个 ob 属性有什么用啊?

你能够观察到,ob 有一个 dep 属性,这个 dep 是否是有点属性,是的,在上一篇基础数据类型中讲过

dep 正是存储依赖的地方

好比 页面引用了 数据child,watch 引用了数据child,那么child 就会把这个两个保存在 dep.subs 中

dep.subs = [ 页面-watcher,watch-watcher ]

可是,在上一篇基础类型种, dep 是做为闭包存在的啊,并非保存在什么【ob.dep】 中啊

没错,这就是 引用类型 和 基础类型的区别了

基础数据类型,只使用 【闭包dep】 来存储依赖

引用数据类型,使用 【闭包dep】 和 【 ob.dep】 两种来存储依赖

什么?你说闭包dep 在哪里?好吧,在 defineReactive 的源码中,你去看看这个方法的源码,下面有

那么,为何,引用类型须要 使用__ob__.dep 存储依赖呢?

首先,明确一点,存储依赖,是为了数据变化时通知依赖,因此 ob.dep 也是为了变化后的通知

闭包 dep 只存在 defineReactive 中,其余地方没法使用到,因此须要保存另一个在其余地方使用

在其余什么地方会使用呢?

在Vue挂载原型上的方法 set 和 del 中,源码以下

function set(target, key, val) {    

    var ob = (target).__ob__;    

    // 通知依赖更新
    ob.dep.notify();
}
Vue.prototype.$set = set;
function del(target, key) {    

    var ob = (target).__ob__;    

    delete target[key];    

    if (!ob)  return

    // 通知依赖更新
    ob.dep.notify();

}
Vue.prototype.$delete = del;

这两个方法,你们应该都用过,为了给对象动态 添加属性和 删除属性

可是若是直接添加属性或者删除属性,Vue 是监听不到的,好比下面这样

child.xxxx=1

delete child.xxxx

因此必需要经过 Vue 包装过的方法 set 和 del 来操做

在 set 和 del 执行完,是须要通知依赖更新的,可是我怎么通知?

此时,【ob.dep】 就发挥做用了!就由于依赖多收集了一份在 ob.dep 中

使用就是上面一句话,通知更新

ob.dep.notify();

二、数组

一、须要遍历数组,可能数组是对象数组,以下面

[{name:1},{name:888}]

遍历时,若是遇到子项是对象的,会跟上面解析对象同样操做

二、给数组保存一个 ob 属性

好比设置一个 arr 数组

公众号

看到 arr数组 加多了一个 ob 属性

公众号

其实这个 ob 属性 和 上一段讲对象 的做用是差很少的,这里也只是说 ob.dep

数组中的 ob.dep 存储的也是依赖,给谁用呢?

给 Vue 封装的数组方法使用,要知道要想数组变化也被监听到,是必须使用Vue封装的数组方法的,不然没法实时更新

这里举重写方法之一 push,其余的还有 splice 等,Vue 官方文档已经有过说明

var original = Array.prototype.push;

Array.prototype.push = function() {    

    var args = [],

    len = arguments.length;    

    // 复制 传给 push 等方法的参数
    while (len--) args[len] = arguments[len];

    // 执行 原方法
    var result = original.apply(this, args);    

    var ob = this.__ob__;    

    // notify change
    ob.dep.notify();    

    return resul
}

看到在执行完 数组方法以后,一样须要通知依赖更新,也就是通知 ob.dep 中收集的依赖去更新

如今,咱们知道了,响应式数据对 引用类型作了什么额外的处理,主要是加了一个 ob 属性

咱们已经知道了 ob 有什么用,如今看看源码是怎么添加 ob

// 初始化Vue组件的数据

function initData(vm) {    

    var data = vm.$options.data;

    data = vm._data = 

        typeof data === 'function' ? 

        data.call(vm, vm) : data || {};

    ....遍历 data 数据对象的key ,重名检测,合规检测
    observe(data, true);

}

function observe(value) {    

    if (Array.isArray(value) || typeof value == "object") {
        ob = new Observer(value);
    }    
    return ob
}
function Observer(value) {   

    // 给对象生成依赖保存器
    this.dep = new Dep();   

    // 给 每个对象 添加一个  __ob__ 属性,值为 Observer 实例
    value.__ob__ = this

    if (Array.isArray(value)) { 

        // 遍历数组,每一项都须要经过 observe 处理,若是是对象就添加 __ob__
        for (var i = 0, l =value.length; i < l; i++) {
            observe(value[i]);
        }

    } else {        

        var keys = Object.keys(value);     

        // 给对象的每个属性设置响应式
        for (var i = 0; i < keys.length; i++) {
            defineReactive(value, keys[i]);
        }
    }
};

源码的流程跟上一篇差很少,只是处理引用数据类型会增长多几行源码的额外处理

咱们以前只说了一种对象数据类型,好比下面这样

公众号

若是会嵌套多层对象呢?好比这样,会怎么处理

公众号

没错,Vue 会递归处理,当遍历属性,使用 defineReactive 处理时,递归调用 observe 处理(源码标红加粗)

若是值是对象,那么一样给 值加多一个 ob

若是不是,那么正常往下走,设置响应式

源码以下

function defineReactive(obj, key, value) {  

    // dep 用于中收集全部 依赖个人 东西
    var dep = new Dep();    
    var val  = obj[key] 

    // 返回的 childOb 是一个 Observer 实例
    // 若是值是一个对象,须要递归遍历对象
    var childOb = observe(val);    

    Object.defineProperty(obj, key, {
        get() {...依赖收集跟初始化无关,下面会讲},
        set() { .... }
    });
}

画一个流程图,仅供参考

公众号

哈哈哈,上面写得好长啊,是有点,可是没办法,想说详细点啊,好吧,还有一段,可是比较短一些哈哈哈,反正看完的人,我jio 得很厉害了,答应我,若是你仔细看完了,评论一下好吗,让我知道有人仔细看了


依赖收集流程

收集流程,就是重点关注 Object.defineProperty 设置的 get 方法了

跟 基础类型数据 对比,引用类型的 收集方法也只是多了几行处理,差别在两行代码

childOb.dep.depend,被我 简单化为 childOb.dep.addSub(Dep.target) dependArray(value) 能够先看下源码,以下

function defineReactive(obj, key, value) {    

    var dep = new Dep();    
    var val  = obj[key]    
    var childOb = observe(val);    

    Object.defineProperty(obj, key, {
        get() {            
            var value = val            
            if (Dep.target) {

                // 收集依赖进 dep.subs
                dep.addSub(Dep.target);

                // 若是值是一个对象,Observer 实例的 dep 也收集一遍依赖
                if (childOb) {
                    childOb.dep.addSub(Dep.target)          
                    if (Array.isArray(value)) {
                        dependArray(value);
                    }
                }
            }            
            return value
        }
    });
}

上面的源码,混杂了 对象和 数组的处理,咱们分开说

一、对象

在数据初始化的流程中,咱们已经知道值是对象的话,会存储多一份依赖在 ob.dep 中

就只有一句话

childOb.dep.depend();

数组还有另一个处理,就是

dependArray(value);

看下源码,以下

function dependArray(value) {    

    for (var i = 0, l = value.length; i < l; i++) {        

        var e = value[i];        

        // 只有子项是对象的时候,收集依赖进 dep.subs
        e && e.__ob__ && e.__ob__.dep.addSub(Dep.target);   
     

        // 若是子项仍是 数组,那就继续递归遍历
        if (Array.isArray(e)) {
            dependArray(e);
        }
    }
}

显然,是为了防止数组里面有对象,从而须要给 数组子项对象也保存一份

你确定会问,为何子项对象也要保存一份依赖?

一、页面依赖了数组,数组子项变化了,是否是页面也须要更新?可是子项内部变化怎么通知页面更新?因此须要给子项对象也保存一份依赖?

二、数组子项数组变化,就是对象增删属性,必须用到Vue封装方法 set 和 del,set 和 del 会通知依赖更新,因此子项对象也要保存

看个栗子

公众号

页面模板

公众号

看到数组的数据,就存在两个 ob

公众号


总结

到这里,就能够很清楚,引用类型和 基础类型的处理差别了

一、引用类型会多添加一个 __ob__属性,其中包含 dep,用于存储 收集到的依赖

二、对象使用 ob.dep,做用在 Vue 自定义的方法 set 和 del 中

三、数组使用 ob.dep,做用在 Vue 重写的数组方法 push 等中

终于写完了,真的好长,可是我以为值得了

公众号

公众号

相关文章
相关标签/搜索