美文网首页
【一起读】深入浅出Vue.js——变化侦测相关的API实现原理

【一起读】深入浅出Vue.js——变化侦测相关的API实现原理

作者: 小妍妍说 | 来源:发表于2021-06-07 09:29 被阅读0次

    4.1 vm.$watch

    用法:

    用于观察一个表达式或者computed函数在Vue.js实例上的变化。回调函数调用时,会从参数得到新数据和旧数据。表达式只接受以点分隔的路径(如a.b.c)。如果是一个比较复杂的表达式,可以用函数代替表达式。

    vm.$watch( expOrFn, callback, [options] )
    
    • expOrFn是被watch的东西,可以是String也可以是Function。
    • 参数deep可以用来发现对象内部值的变化,监听数组的变动时用不到。
    • 参数immediate表示是否立即以表达式的当前值触发回调。
    原理:

    vm.$watch是对Watcher的一种封装

    Vue.prototype.$watch=function(expOrFn, cb, options){
          const vm=this
          options=options || {}
          const watcher =new Watcher(vm, expOrFn, cb, options)
          if(options.immediate){
            cb.call(vm, watcher.value)
          }
          return function unwatchFn(){
            watcher.teardown()
          }
        }
    

    代码解析

    • 通过Watcher完全可以实现vm.$watch的功能,但是vm.$watch中的参数deep和immediate是Watcher中所没有的。

    修改:新增判断expOrFn类型的逻辑。如果expOrFn是函数,则直接将它赋值给getter;如果不是函数,再使用parsePath函数来读取keypath中的数据。

    export default class Watcher{
          constructor (vm, expOrFn, cb){
            this.vm=vm
            // expOrFn参数支持函数
            if(typeof expOrFn==='function'){
              this.getter=expOrFn
            }else{
              this.getter=parsePath(expOrFn)
            }
            this.cb=cb
            this.value=this.get()
          }
          ......
        }
    

    代码解析

    • 当expOrFn是函数时,不只可以动态的返回数据,其中读取的所有数据也都被Watcher观察。当expOrFn时字符串类型的keypath时,Watcher会读取这歌keypath所指向的数据并观察这个数据的变化。而当expOrFn是函数时,Watcher会同时观察expOrFn函数中读取的所有Vue.js实例上的响应式数据。即如果函数从Vue.js实例上读取了两个数据,那么Watcher会同时观察这两个数据的变化,当其中任意一个发生变化时,Watcher都会得到通知。
    • 执行new Watcher后,代码会判断用户是否使用了immediate函数,若是,则立即执行一次cb。
    • 返回函数unwatchFn表示取消观察数据。执行的watcher.teardown()本质是把watcher实例从当前正在观察的状态的依赖列表中移除。

    teardown()函数的实现:首先在WAtcher中记录自己都订阅了谁,即watcher实例被收集进了哪些Dep中。若不想继续订阅,循环自己记录的订阅列表来通知Dep将自己从Dep的依赖列表中移除。

    export default class Watcher{
          constructor (vm, expOrFn, cb){
            this.vm=vm
            this.deps=[]  //新增
            this.depIds=new Set()
            this.getter=parsePath(expOrFn)
            this.cb=cb
            this.value=this.get()
          }
          ......
          addDep(dep){
            const id=dep.id
            if(!this.depIds.has(id)){
              this.depIds.add(id)
              this.depIds.push(dep)
              dep.addSub(this)
            }
          }
         ...
        }
    

    实现思路:

    • 先在Watcher中添加addDep方法,该方法的作用是在Watcher中记录自己订阅过的Dep。
    • depIds用来判断如果当前Watcher已经订阅了该Dep,则不会重复订阅。(因为Watcher每次读取value都会触发收集依赖的逻辑。当依赖发生变化时,会通知Watcher重新读取最新的数据,如果诶一判断,就会导致每当数据发生变化,Watcher都会读取最新的数据,就导致Dep中的依赖有重复。这样当数据发生变化时,会通知多个Watcher。为避免这个问题,只有第一次触发getter的时候才会收集依赖。)
    • 执行this.depIds.add来记录当前Watcher已经订阅了这个Dep。
    • 执行this.deps.push(dep)记录自己都订阅了哪些Dep。
    • 触发dep.addSub(this)来将自己订阅到Dep中。

    在Watcher中新增addDep方法后,Dep中收集依赖的逻辑也需要有所改变:

    let uid=0
        export default class Dep{
          constructor(){
            this.id=uid++   //新增
            this.subs=[]
          }
          ......
          depend(){
            if(window.target){
              //  this.addSub(window.target)  //废弃
              window.target.addDep(this)  //新增
            }
          }
        }
    

    Dep会记录数据发生变化时,需要通知哪些Watcher,而Watcher中也同样记录了自己会被哪些Dep通知。
    现在正式介绍teardown()函数

    teardown(){
          let i=this.deps.length
          while(i--){
            this.deps[i].removeSub(this)
          }
        }
    
    export default class Dep{
      
          ......
          removeSub(sub){
            const index=this.subs.indexOf(sub)
            if(index>-1){
              return this.subs.splice(index,1)
            }
          }
          ......
        }
    

    相关文章

      网友评论

          本文标题:【一起读】深入浅出Vue.js——变化侦测相关的API实现原理

          本文链接:https://www.haomeiwen.com/subject/owdxeltx.html