欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Vue关于Watch源码的学习笔记(编辑中)

程序员文章站 2022-03-28 12:31:55
...

Vue中Watch的源码相比于Compute的源码实现简单了很多,以下是我的学习笔记:

一、初始化

1、初始Vue时会通过initState方法,在代码中进行opts.watch字段的判断,从而进行initState方法对Watch进行初始化。(和Compute的一样)。

// 部分实现
function Vue(){
    ... 其他处理
    initState(this)
    ...解析模板,生成DOM 插入页面
}

function initState(vm) {
    ...处理 data,props,computed 等数据
    if (opts.watch) {
        initWatch(this, vm.$options.watch);
    }
}

2、initWatch分析为每个watch创建专门的watcher也就是createWatcher 

// 部分实现
function initWatch(vm, watch) {    
    for (var key in watch) {    
        var watchOpt = watch[key];
        createWatcher(vm, key, handler);
    }
}

function createWatcher(
    // expOrFn 是 key,handler 可能是对象
    vm, expOrFn, handler,opts
) { 
    // 监听属性的值是一个对象,包含handler,deep,immediate
    // 下面的判断就是获取用户定义的watch函数
    if (typeof handler ==="object") {
        opts= handler
        handler = handler.handler
    }    
    // 回调函数是一个字符串,从 vm 获取
    if (typeof handler === 'string') {
        handler = vm[handler]
    }    
    // expOrFn 是 key,options 是watch 的全部选项
    vm.$watch(expOrFn, handler, opts)
}

3、 vm.$watch(),这里才是watch-watcher的细节实现


  Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
  ): Function {
    const vm: Component = this
    if (isPlainObject(cb)) {
      return createWatcher(vm, expOrFn, cb, options)
    }
    // 为watch的watcher添加user标志
    options = options || {}
    options.user = true
    const watcher = new Watcher(vm, expOrFn, cb, options)
    // immediate为true时直接回调
    if (options.immediate) {
      try {
        cb.call(vm, watcher.value)
      } catch (error) {
        handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
      }
    }
    ......
  }

我们可以看到当 immediate设置为true时,在读取了 监听的数据的值 之后,便立即调用一遍你设置的监听回调,然后传入刚读取的值。

假设有个data为a,当a被初始化时,其实这个a的 Dep 就直接收集到了 a 对应的 watch-watche

二、依赖收集与更新

观察 Watcher 源码:

export default class Watcher {
  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    this.vm = vm
    // options
    if (options) {
      this.deep = !!options.deep
      this.user = !!options.user
      this.lazy = !!options.lazy
      this.sync = !!options.sync
      this.before = options.before
    } else {
      this.deep = this.user = this.lazy = this.sync = false
    }
    this.cb = cb
    this.id = ++uid // uid for batching
    this.active = true
    this.dirty = this.lazy // for lazy watchers
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()
    // 对于watch-watcher来说这一步就相当于拿hanlder
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
    }
    // 当 watch-watcher 到这一步时,直接调用了 this.get, 相当于直接调用了watch的hanlder
    this.value = this.lazy
      ? undefined
      : this.get()
  }
}

简单来说当watch-watcher进入时就会触发 this.get()

get () {
  pushTarget(this)
  let value
  const vm = this.vm
  try {
    // 这一步相当于调用watch的hanlder
    value = this.getter.call(vm, vm)
  } catch (e) {
    if (this.user) {
      handleError(e, vm, `getter for watcher "${this.expression}"`)
    } else {
      throw e
    }
  } finally {
    // 若定义了deep,这会遍历watch监听的那个对象的所有key值
    if (this.deep) {
    // 递归每一个对象或者数组,触发它们的getter,
    // 使得对象或数组的每一个成员都被依赖收集,形成一个“深(deep)”依赖关系
      traverse(value)
    }
    popTarget()
    this.cleanupDeps()
  }
  return value
}

因为this.get(),需要获取到监听的值,这样就相当于触发了监听值的getter,从而触发了 监听值的 dep.depend()

又因为这时候的 Dep.target 指向的是 watch-watcher,这样就相当于监听值 收集到 watch-watcher 进了它的 Dep里

相关标签: Vue深入学习