Vue源碼簡析之Watcher(上)

在Vue中,Watcher是個很重要的概念,一共有三種Watcher,renderWatcher,userWatcher,computedWatcher,以前的文章有提到renderWatcher,主要是生成視圖,本文聊下userWatcherexpress

使用方式

<template>
  <div class="container">
    <div class="a">{{msg.name}}</div>
    <button @click="handleClick">click</button>
  </div>
</template>
<script>
export default {
  data() {
    return {
      msg: 'hello'
    }
  },
  watch: {
    msg(nv, ov) {
        console.log('user watch emit', nv, ov)
      }
  },
  methods: {
    handleClick() {
      this.msg = this.msg === 'hello' ? 'world' : 'hello'
    }
  }
}
</script>
複製代碼

這裏定義了一個watcher,當this.msg變化時會觸發回調,參數是當前值和舊值。數組

watcher初始化

// core/instance/init.js
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
複製代碼

initState

// core/instance/state.js
export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

複製代碼

initWatch

// // core/instance/state.js
function initWatch (vm: Component, watch: Object) {
  for (const key in watch) {
    const handler = watch[key]
    if (Array.isArray(handler)) {
      for (let i = 0; i < handler.length; i++) {
        createWatcher(vm, key, handler[i])
      }
    } else {
      createWatcher(vm, key, handler)
    }
  }
}
複製代碼

能夠看到,watch是支持傳入回調函數數組的bash

createWatcher

function createWatcher (
  vm: Component,
  expOrFn: string | Function,
  handler: any,
  options?: Object
) {
  if (isPlainObject(handler)) {
    options = handler
    handler = handler.handler
  }
  if (typeof handler === 'string') {
    handler = vm[handler]
  }
  return vm.$watch(expOrFn, handler, options)
}
複製代碼

watch支持對象配置,handler是回調函數。ide

Vue.$watch

// 
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)
    }
    options = options || {}
    options.user = true
    const watcher = new Watcher(vm, expOrFn, cb, options)
    if (options.immediate) {
      cb.call(vm, watcher.value)
    }
    return function unwatchFn () {
      watcher.teardown()
    }
  }
}
複製代碼
  1. options.immediate爲true時回調函數會當即執行一次。
  2. $watch返回一個函數,執行該函數能夠取消watch

new Watcher

// core/observer/watcher.js
export default class Watcher {
  vm: Component;
  expression: string;
  cb: Function;
  id: number;
  deep: boolean;
  user: boolean;

  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    this.vm = vm
    if (isRenderWatcher) {
      vm._watcher = this
    }
    vm._watchers.push(this)
    // options
    if (options) {
      this.deep = !!options.deep
      this.user = !!options.user
      this.computed = !!options.computed
      this.sync = !!options.sync
      this.before = options.before
    } else {
      this.deep = this.user = this.computed = this.sync = false
    }
    ...

    // parse expression for getter
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
      ...
    }
    if (this.computed) {
      this.value = undefined
      this.dep = new Dep()
    } else {
      this.value = this.get()
    }
  }

  /** * Evaluate the getter, and re-collect dependencies. */
  get () {
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm)
    } catch (e) {
      if (this.user) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      } else {
        throw e
      }
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      if (this.deep) {
        traverse(value)
      }
      popTarget()
      this.cleanupDeps()
    }
    return value
  }

複製代碼

這裏結合debug看下函數

expOrFn是字符串'msg',會走this.getter = parsePath(expOrFn),返回一個函數,而後執行this.get(), 接着value = this.getter.call(vm, vm),來看下getter的值。ui

parsePath

// core/utils/lang.js
const bailRE = /[^\w.$]/
export function parsePath (path: string): any {
  if (bailRE.test(path)) {
    return
  }
  const segments = path.split('.')
  return function (obj) {
    for (let i = 0; i < segments.length; i++) {
      if (!obj) return
      obj = obj[segments[i]]
    }
    return obj
  }
}
複製代碼

parsePath對key進行解析,咱們傳入的值是msg,返回一個函數,在watch的get方法中調用,入參obj = vm,第一個循環中obj = obj[segments][i]觸發了vm.msg的getter,收集依賴,即當前的userWatch(這部分流程須要熟悉Vue的響應式實現)this

當值改變時

handleClick() {
  this.msg = this.msg === 'hello' ? 'world' : 'hello'
}
複製代碼

改變this.msg時會觸發對應的setter,遍歷依賴依次更新,msg這個key對應的依賴項有兩個,一個renderWatch和一個userWatch,能夠debug看下。lua

watcher更新時會調用run()spa

run

// core/observer/watcher.js
run () {
    if (this.active) {
      this.getAndInvoke(this.cb)
    }
  }
複製代碼

getAndInvoke

// core/observer/watcher.js
getAndInvoke (cb: Function) {
    const value = this.get()
    if (
      value !== this.value ||
      // Deep watchers and watchers on Object/Arrays should fire even
      // when the value is the same, because the value may
      // have mutated.
      isObject(value) ||
      this.deep
    ) {
      // set new value
      const oldValue = this.value
      this.value = value
      this.dirty = false
      if (this.user) {
        try {
          cb.call(this.vm, value, oldValue)
        } catch (e) {
          handleError(e, this.vm, `callback for watcher "${this.expression}"`)
        }
      } else {
        cb.call(this.vm, value, oldValue)
      }
    }
  }
複製代碼

getAndInvoke中拿到當前值和舊值,若是是userWatch會執行cb.call(this.vm, value, oldValue),也就是咱們的回調函數prototype

總結

userWatcher的初始化發生在beforeCreatecreated以前,new一個Watcher實例並將其添加進對應的key的依賴數組中,當監聽的值發生變化時觸發watcher的更新,執行回調函數。

下一篇聊下computed的實現

相關文章
相關標籤/搜索