【Vue 源码学习笔记】-- Watch

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时,在读取了 监听的数据的值 之后,便立即调用一遍你设置的监听回调,然后传入刚读取的值。

二、依赖收集与更新

观察 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的handler
    this.value = this.lazy
      ? undefined
      : this.get()
  }
}

1、依赖收集

简单来说当watch-watcher进入时就会触发 this.get(),也就是用户自己定义的 handler

get () {
  pushTarget(this)
  let value
  const vm = this.vm
  try {
    // 这一步相当于调用watch的handler
    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里

所以当 监听值变化时,就会触发 watch, 这样监听就成功了

2、触发更新

由上面的分析我们知道监听的数据变化的时候,就能通知 watch-watcher 更新,所谓通知更新,就是手动调用 watch.update

以下为 watcher.update 部分源码:

Watcher.prototype.update= function() {    
    var value = this.get();    
    if (this.deep) {        
        var oldValue = this.value;        
        this.value = value;        
        // cb 是监听回调
        this.cb.call(this.vm, value, oldValue);
    }
};

很简单嘛,就是读取一遍值,然后保存新值,接着 调用 监听回调也就是用户定义的handler,并传入新值和 旧值

 

你可能感兴趣的:(Vue深入学习,1024程序员节)