【Vue源码】变化侦测篇-Objcet的依赖收集

依赖收集

什么是依赖收集

当数据变得可观测后,我们就能知道数据什么时候发生变化,那么当数据发生变化的时候,我们去通知视图更新就好了。那么问题来了,我们如何知道该更新哪一个视图呢,不可能以为一个数据变化把整个视图都更新掉。

方法就是谁依赖了这个数据就更新谁,通俗易懂的将就是给每个可观测的数据都建立一个依赖数组(因为一个数据可能会多次使用),谁用到了这个数据,就把谁放到这个数组中,当数据变化时,我们只需要将依赖数组中的依赖都通知一遍即可。

何时收集依赖?何时通知依赖更新

结合Object.defineproperty()方法将数据变得可观测后,我们就可以在getter/setter中来收集依赖以及通知依赖更新

把依赖收集到哪里

单单一个依赖数组的功能不足以支撑依赖收集的功能,所以VUE为每一个数据都建立了一个依赖管理器,他扩展了依赖数组的功能还把所有的依赖都管理起来,Dep类就是VUE的依赖管理器

//源码位置: src/core/observer/dep.js

export default class Dep{
    constructor (){
        this.subs = [] //依赖数组
    }

    addSub(sub){
        this.subs.push(sub)
    }

    //删除一个依赖
    removeSub(sub){
        remove(this.subs,sub)
    }

    //添加一个依赖
    depend(){
        //window.target以后会讲到,现在先不做解释
        if(window.target){
            this.addSub(window.target)
        }
    }

    //通知所有以来更新
    notify() {
        cosnt subs = this.subs.slice()
        fot(let i = 0 ; i < subs.length; i++){
            subs[i].update
        }
    }
}

/**
 * 从数组中删除元素
 */
export function remove(arr,item){
    if(arr.length){
        const index = arr.indexOf(item)
        if(index > -1){
            return arr.splice(index, 1)
        }
    }
}

在上方的依赖管理器Dep类中,我们初始化了一个subs数组,用来存放依赖,并定义了几个实例方法来对依赖进行添加、删除、通知等操作

有了依赖管理后我们就可以在getter中进行收集依赖,setter中通知依赖更新了

function defineReactive(obj,key,val){
    if(arguments.length == 2){
        val = obj[key]
    }

    if(typeof val === 'Object'){
        new Observer(val)
    }

    cosnt dep = new Dep() //实例化一个依赖管理器,生成一个依赖管理数组dep

    Object.defineProperty(obj,key,{
        enumerable: true,
        configurable: true,
        get(){
            dep.depend() //在getter中收集依赖
            return val
        },
        set(newVal){
            if(val === newVal){
                return
            }
            val = newVal;
            dep.notify() //在setter中通知依赖更新
        }
    })
} 

依赖到底是谁

所谓的依赖,就是谁用到了这个数据谁就是依赖,那么在代码上如何描述依赖呢。

在VUE中实现了一个叫做watcher的类,而watcher类的实例就是我们上面所说的那个谁?

简单的来说:谁用到了数据,谁就是依赖,就为谁创建一个watcher实例。在之后的数据变化时,我们不直接去通知以来更新,而是通知依赖对应的watch实例,有watcher实例去通知真正的视图。

watcher类的具体实现

/**
 * @param { Object } vm vue实例
 * @param { String } expOrFn 数据的路径
 * @param { Function } cb 回调
 */
export default class Watcher{
    constructor(vm,expOrFn,cb){
        this.vm = vm;
        this.cb = cb;
        this.getter = parsePath(expOrFn) //用于在vue实例中找到数据,返回的是一个方法
        this.value = this,get();//触发数据的getter

        get(){
            //将当前实例存储到全局对象中去,以便observer实例的get直接在全局中拿到
            cosnt vm = this.vm;
            window.target = this; 
            //使用返回的方法从vue实例中找到数据,顺便通过访问触发该数据的getter方法,触发后即可收集进依赖数组中
            let value = this.getter.call(vm,vm)
            window.target = undefined //释放全局对象
            return value
        }
        update(){
            const oldValue = this.value
            this.value = this.get()
            this.cb.call(this.vm,this.value,oldValue)
        }
    }
}

/**
 * Parse simple path
 * 把一个形如'a.b.c'的字符串路径所表示的值,从真是的data对象中取出来
 * 例如:
 * data = {a: {b: {c:2}}}
 * parsePath('a.b.c')(data) //2
 */
cosnt bailRE = /[^\w.$]/ //去除字符串中的违法字符
export function parsePath(path){
    if (bailRe.test(path)){
        return
    }
    cosnt segments = path.split('.')
    return function(obj){
        for(let i = 0;i<segments.length;i++){
            if(!obj) return
            obj = obj[segments[i]]
        }
        return obj
    }
}
  • 以下为个人观点:当在渲染dom的时候,会为使用了数据的视图(当前组件的vue实例),在watcher的构造函数中,会访问当前被使用的数据,所以会触发Observer实例的getter方法,并将当前使用的数据的watcher实例通过Dep实例的依赖收集方法放入到当前数据的Dep实例所维护的依赖数组中,并且在数据被改变之后,会触发当前数据的Observer实例的setter方法,在setter方法中会通过Dep实例的通知依赖更新方法,来触发watcher实例的update方法来更新视图,这里的视图(vm)指的就是当前的vue实例。
  • 流程:
    1、Data通过observer转换成了getter/setter的形式来追踪变化
    2、当外界通过watcher读取数据时,会触发getter从而将watcher添加到依赖中
    3、当数据发生了变化时,会触发setter,从而想Dep中的依赖发送通知。
    4、watcher接收到通知后,会向外界发送通知,变化通知到外界后可能会触发试图更新,也可能触发用户的某个回调函数等

不足之处

虽然我们通过Object.defineProperty方法实现了对object数据的可观测,但是这个方法仅仅只能观测到object数据的取值及设置值,当我们向object数据里添加一对新的key/value或删除一对已有的key/value时,他是无法观测到的,导致当我们对object数据添加或删除值时,无法通知依赖,无法驱动试图进行响应式更新。为了解决这一问题,VUE增加了两个全局API:Vue.set和Vue.delete.

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值