vue源码——object的变化侦测

什么是变化侦测

渲染:
vue.js会自动通过状态生成DOM,并将其输出到页面上显示出来

在运行时应用内部状态不断变化,此时需要不断渲染。那我们如何确定发生了哪些变化?
这个时候就需要变化侦测了。

变化侦测

  • 拉:Angular、React,当状态发生变化时,它只知道状态变了,给框架发送信息,而框架内部则进行暴力对比找出需要渲染的DOM节点,在Angular属于脏检查,在React使用的是虚拟DOM。
  • 推:Vue.js,当状态发生变化,vue立刻就知道了,而且在一定程度指导的消息更多,可以进行更细粒度的更新。

更细粒度的更新:假如有一个状态绑定了好多个依赖,每个依赖表示一个具体的DOM节点,,当状态发生变化,向依赖发送通知,进行DOM更新,相较而言,拉的粒度是最粗的。当然粒度越细,每个状态绑定的依赖就越多,依赖追踪在内存中的开销就越大,因此从Vue2.0开始就调整依赖绑定的粒度为组件。通知到组件之后,组件内部再用虚拟DOM进行对比。这样可以大大降低依赖数量,从而降低依赖追踪所消耗的内存。

如何追踪变化

追踪变化有两个方法:Object.defineProperty和ES6的Proxy

根据Object.defineProperty可以侦测到对象的变化,可以写出如下代码

function(data,key,val){
//参数123,1:定义属性的对象,2:定义或修改的属性的名称
//,3.定义或修改的属性描述符
    Object.defineProperty(data,key,{
        enumerable:true,
        configurable:true,
        get:function(){
            return val
        },
        set:function(newVal){
            if(val===newVal){
                return
            }
            val=newVal
        }
    })
}

函数defineReactive用来对Object.defineProperty进行封装。
作用:定义一个响应式数据,也就是在这个函数中进行变化跟踪,封装后只需要传递data,key,val就行了。
封装好之后每当从data的key中读取数据时,get属性杯触发;每当往data的key中设置数据时set函数被触发

如何收集依赖

收集依赖就是把用到数据的地方收集起来,然后等属性发生变化时,把之前收集好的依赖循环触发一遍就好了。
就是

在getter中收集,在setter中触发依赖

依赖收集在哪里

依赖收集代码封装成一个Dep类,专门管理依赖。可以收集依赖、删除依赖或向依赖发送通知等。

dep类

function defineReactive(data,key,val){
    let dep=new dep()//修改
    Object.defineProperty(data,key,{
        enumerable:true,
        configurable:true,
        get:function(){
            dep.depend()//修改
            return val
        },
        set:function(newVal){
            if(val===newVal){
                return
            }
            val=newVal
            dep.notify()//新增
        }
    })
}

export default class Dep{
    constructor(){
        this.subs=[]
    }
    
    addSub(sub){
        this.subs.push(sub)
    }

    removeSub(sub)(
        remove(this.subs,sub)
    )

    depend(){
        if(window.target)//target:节点
        {
            this.addSub(window.target)
        }
    }

    notify(){
      //选取数组的一部分,并返回一个新数组。
        const subs=this.subs.slice()
      
        for(let i=0;i<subs.length;i++){
            subs[i].updata()
        }
    }

    function remove(arr,item){
        if(arr.length){
            const index =arr.indexOf(item)
            if(index>-1){
              //从数组中添加或删除元素。删除从索引inidex后的一个元素
                return arr.splice(index,1)
              
            }
        }
    }
}

defineReactive

function defineReactive(data,key,val){
    let dep=new dep()//修改
    Object.defineProperty(data,key,{
        enumerable:true,
        configurable:true,
        get:function(){
            dep.depend()//修改 添加依赖
            return val
        },
        set:function(newVal){
            if(val===newVal){
                return
            }
            val=newVal
            dep.notify()//新增,通知依赖
        }
    })
}

依赖是谁

在上面的代码里window.target是我们的依赖,那么它又是什么呢?
我们使用数据的地方很多,类型也不太一样,可能是模板,也可能是用户写的watch,我们可以抽象出一个处理这些情况的类叫watcher

所以我们要收集的就是watcher

什么是watcher

Watcher是一个中介角色,数据发生变化时通知它,然后它再通知其他地方。

vm.$watch('a.b.c'),function(newVal,oldVal){
}

这段代码表示当data.a.b.c发生变化是,触发第二个参数中的函数

怎么去实现这个功能呢?是不是和上面讲的把watcher实例添加到data.a.b.c属性的dep中就可以了。这样子,当data.a.b.c属性的值发生变化时,通知watcher,接着watcher再执行回调函数。

export default class Watcher{

    constructor(vm,expOrFn,cb){
        this.vm=vm
        //执行this.getter(),就饿可以读取data.a.b.c的内容
        this.getter=ParsePath(expOrFn)
        this.cb=cb
        this.value=value
    }

    get(){
        window.target=this
        let value=this.getter.call(this.vm,this.vm)
        window.target=undefined
        return value
    }
    update(){
        const oldVlaue=this.value
        this.value=this.get()
        this.cb.call(this.vm,this.value,oldValue)
    }
}

这段代码作用:把自己主动添加到data.a.b.c的Dep中
在get方法中把window.target设置成了this,也就是当前watcher实例,然后再读一下data.a.b.c的值,这会触发getter,也就触发收集依赖的逻辑。从window.target中读取一个依赖并添加到dep中
也就是说只要现在window.target赋一个this,然后再读一下值,触发getter就可以把this主动添加到keypath的Dep中。
依赖注入到Dep中后、每当data.a.b.c的值发生变化,就会让依赖列表中的所有依赖循环调用update方法,也就是watcher中的updata方法。而update方法会执行参数中的回调函数,将value和oldValue传到参数中

递归侦测所有key

前面已经实现了侦测数据中某一个属性,现在希望把数据中的所有属性都侦测到,所以封装一个
Observer:这个类的作用是将一个数据内的所有属性(包括子属性)都转换成getter/setter的形式,然后去追踪他们的变化:

export class Observer{
    constructor (value){
        this.value=value

        if(!Array.isArray(value)){
            this.walk(value)
        }
    }
    /*walk会将每一个属性都转换成getter/setter的形式来侦测变化
    *这个方法只有在数据类型为object时被调用
    *
    */ 
   walk(obj){
       const keys=Object.keys(obj)
       for(let i=0 ; i<keys.length;i++){
           defineReactive(obj,keys[i],obj[keys[i]])
       }
   }

   function defineReactive(data,key,val){
       //新增递归子属性
       if(typeof val === 'object')
       {
       new Observer(val)
       }
       let dep=new dep()
       Object.defineProperty(data,key,{
           enumerable:true,
           configurable:true,
           get:function(){
               dep.depend()
               return val
           },
           set:function(newVal){
               if(val===newVal){
                   return
               }
               val=newVal
               dep.notify()
           }
       })
   }

}

Observer类:用来将一个正常的object转换成被侦测的object
只有object类的数据才会调用walk将每一个属性转换成getter/setter的形式来侦测变化
在defineReactive中新增 new Observer(val)来递归子属性,这样就可以data中的所有属性都转换成getter/setter形式来侦测变化
只要我们将一个object传到observer中,那么这个object就会变成响应式的object.

object的问题

因为是通过setter和getter来侦测变化,所以如果是新增属性则无法侦测,删除也是

为了解决这个问题vue.js引入了两个API——vm.$set与vm.delete

引用申明

本博文为深入浅出vue.js的学习笔记,若侵则删。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值