资讯专栏INFORMATION COLUMN

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

vvpvvp / 1876人阅读

摘要:写文章不容易,点个赞呗兄弟专注源码分享,文章分为白话版和源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧研究基于版本如果你觉得排版难看,请点击下面链接或者拉到下面关注公众号也可以吧原理依赖收集源码版之引用数据类型上

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

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

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

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

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

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

文章很长,高能预警,做好准备耐下心好,肯定还是有点收获的

但是两个类型的数据的处理,又有很多重复的地方,所以打算只写一些差异性的地方就好了,否则显得废话很多

两个步骤,都有不同的地方

1、数据初始化

2、依赖收集

数据初始化流程

如果数据类型是引用类型,需要对数据进行额外的处理。

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

1对象

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

2、每个数据对象会增加一个 ob 属性

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

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

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();

2、数组

1、需要遍历数组,可能数组是对象数组,如下面

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

遍历时,如果遇到子项是对象的,会跟上面解析对象一样操作

2、给数组保存一个 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
        }
    });
}

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

1、对象

在数据初始化的流程中,我们已经知道值是对象的话,会存储多一份依赖在 __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);
        }
    }
}

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

你肯定会问,为什么子项对象也要保存一份依赖?

1、页面依赖了数组,数组子项变化了,是不是页面也需要更新?但是子项内部变化怎么通知页面更新?所以需要给子项对象也保存一份依赖?

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

看个栗子

页面模板

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

总结

到这里,就可以很清楚,引用类型和 基础类型的处理差异了

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

2、对象使用 __ob__.dep,作用在 Vue 自定义的方法 set 和 del 中

3、数组使用 __ob__.dep,作用在 Vue 重写的数组方法 push 等中

终于写完了,真的好长,但是我觉得值得了

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/109952.html

相关文章

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

    摘要:当东西发售时,就会打你的电话通知你,让你来领取完成更新。其中涉及的几个步骤,按上面的例子来转化一下你买东西,就是你要使用数据你把电话给老板,电话就是你的,用于通知老板记下电话在电话本,就是把保存在中。剩下的步骤属于依赖更新 写文章不容易,点个赞呗兄弟专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧研究基于 Vue版本 【...

    VincentFF 评论0 收藏0
  • Vue原理依赖更新 - 源码

    摘要:写文章不容易,点个赞呗兄弟专注源码分享,文章分为白话版和源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧研究基于版本如果你觉得排版难看,请点击下面链接或者拉到下面关注公众号也可以吧原理依赖更新源码版如果对依赖收集完 写文章不容易,点个赞呗兄弟专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧研究基于...

    moven_j 评论0 收藏0
  • Vue原理】Props - 源码

    写文章不容易,点个赞呗兄弟专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧研究基于 Vue版本 【2.5.17】 如果你觉得排版难看,请点击 下面链接 或者 拉到 下面关注公众号也可以吧 【Vue原理】Props - 源码版 今天记录 Props 源码流程,哎,这东西,就算是研究过了,也真是会随着时间慢慢忘记的。 幸好我做...

    light 评论0 收藏0
  • Vue原理】NextTick - 源码版 之 服务Vue

    写文章不容易,点个赞呗兄弟专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧研究基于 Vue版本 【2.5.17】 如果你觉得排版难看,请点击 下面链接 或者 拉到 下面关注公众号也可以吧 【Vue原理】NextTick - 源码版 之 服务Vue 初次看的兄弟可以先看 【Vue原理】NextTick - 白话版 简单了解下...

    Acceml 评论0 收藏0
  • Vue原理】VModel - 源码版之input详解

    摘要:因为失去焦点之后被强制更新了一波嗯,这就是的作用,把页面上的显示值也过滤一遍 写文章不容易,点个赞呗兄弟专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧研究基于 Vue版本 【2.5.17】 如果你觉得排版难看,请点击 下面链接 或者 拉到 下面关注公众号也可以吧 【Vue原理】VModel - 源码版之input详...

    leanxi 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<