Vue.watche 原始碼實現

進階__前端漫漫路發表於2021-01-03

watch

vm.$watch(expOrFn,callback,[options])
功能

觀察Vue例項變化的一個表示式或計算屬性函式。回撥函式得到的引數為新值和舊值。表示式值接收監督的鍵路徑

引數

  • exOrFn:要監視的$data 中的屬性,可以說表示式或函式
  • callback:資料變化後執行的函式
    函式:回撥函式
    物件:具有handler屬性(字串或者函式),如果該屬性為字串則methods中相應的定義
  • options:可選的選項
    deep: 布林型別,深度監聽
    immediate: 布林型別,是否立即執行一次回撥函式

示例

const vm = new Vue({
   el:"#app",
   data:{
     user:{
        fisrtName: 'aaaa',
        lastName: 'bbbbb'
      }
   }
})

vm.$watch('user',function(newValue,oldValue){
   this.user.firstName = newValue.firstName + "  " + newValue.lastName
},{
 immediate: true,
 deep: true
})

三種型別的Watcher物件

  • 沒有靜態方法,因為$watch方法中要是用Vue的例項
  • Watcher分三種:計算屬性Watcher,使用者Watcher(偵聽器)、渲染Watcher
    建立順序:計算屬性Watcher,用於Watcher,渲染Watcher
  • vm.$watch()
    src/core/instance/state.js
    部分程式碼
export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props) // 初始化props,並且通過defineReactive函式將值轉換為set,get
  if (opts.methods) initMethods(vm, opts.methods) // 將選項中的methods注入到vue例項,
  if (opts.data) {
    initData(vm) // 
  } else {
    observe(vm._data = {}, true /* asRootData */) //轉換成響應式資料
  }  
  if (opts.computed) initComputed(vm, opts.computed)  // 初始化computed
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)  // 初始化watch
  }
}


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)
    }
  }
}

function createWatcher (
  vm: Component,
  expOrFn: string | Function,
  handler: any,
  options?: Object
) {
  if (isPlainObject(handler)) { // hundler是回撥函式,就是對應傳入方法
    options = handler
    handler = handler.handler
  }
  if (typeof handler === 'string') {
    handler = vm[handler]
  }
  return vm.$watch(expOrFn, handler, options)
}

export function stateMixin (Vue: Class<Component>) {
  // flow somehow has problems with directly declared definition object
  // when using Object.defineProperty, so we have to procedurally build up
  // the object here.
  const dataDef = {}
  dataDef.get = function () { return this._data }
  const propsDef = {}
  propsDef.get = function () { return this._props }
  if (process.env.NODE_ENV !== 'production') {
    dataDef.set = function () {
      warn(
        'Avoid replacing instance root $data. ' +
        'Use nested data properties instead.',
        this
      )
    }
    propsDef.set = function () {
      warn(`$props is readonly.`, this)
    }
  }
  Object.defineProperty(Vue.prototype, '$data', dataDef)
  Object.defineProperty(Vue.prototype, '$props', propsDef)

  Vue.prototype.$set = set
  Vue.prototype.$delete = del

  Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
  ): Function {
    // 獲取 Vue 例項的this
    const vm: Component = this
    if (isPlainObject(cb)) {
      // 判斷如果 cb 是物件執行createWatcher
      return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {}
    // 標記為使用者 watcher
    options.user = true
    // 建立使用者 watcher 物件
    const watcher = new Watcher(vm, expOrFn, cb, options)
    // 判斷 immediate 物件
    if (options.immediate) {
      // 立即執行一次cb回撥,並把當前值傳入
      try {
        cb.call(vm, watcher.value)
      } catch (error) {
        handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
      }
    }
    // 返回取消監聽的方法
    return function unwatchFn () {
      watcher.teardown()
    }
  }
}

相關文章