Skip to content
On this page

侦听属性

侦听属性的初始化

同样是在 initState 的时候

js
// instance/state.js
if (opts.watch && opts.watch !== nativeWatch) {
  initWatch(vm, opts.watch)
}

initWatch

js
// instance/state.js
function initWatch (vm, watch) {
  for (const key in watch) {
    const handler = watch[key]
    if (Array.isArray(handler)) {
      for (let i = 0; i < handler.length; i++) { 
        // 如果watch的定义中handler是个数组
        createWatcher(vm, key, handler[i])
      }
    } else {
      createWatcher(vm, key, handler)
    }
  }
}

createWatcher

js
function createWatcher (vm, expOrFn, handler, options) {
  if (isPlainObject(handler)) {
    // 参数规范化
    options = handler;
    handler = handler.handler;
  }
  if (typeof handler === 'string') {
    handler = vm[handler] // 回调是vm实例中的method
  }
  return vm.$watch(expOrFn, handler, options) // 最终的调用$watch
}

$watch

js
// instance/state.js
Vue.prototype.$watch = function (expOrFn, cb, options) {
  const vm = this // vm
  if (isPlainObject(cb)) {
    return createWatcher(vm, expOrFn, cb, options) // 组件内手动调用的$watch
  }
  opions = options || {}
  options.user = true
  const watcher = new Watcher(vm, expOrFn, cb, options) // userWatcher
  if (options.immediate) {
    try {
      cb.call(vm, watcher.value) 
      // 如果immediate=true,立即执行一次,注意这时候还没挂载,还在initState的调用栈内
    }
  }
  return function unwatchFn () {
    watcher.teardown()
  }
}

userWatcher

js
// observer/watcher.js
class Watcher {
  contructor(vm, expOrFn, options, isRenderWatcher) {
    // expOrFn 则是要观察的数据对象key,一般为字符串
    if (options) {
      this.user = !!opions.user // true
    }
    if (typeof expOrFn === 'function') {
      // 其实观察的key也可以是个函数,相当于观察一个computed的值
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn) // 根据字符串返回值
    }
    this.value = this.lazy ? undefined : this.get() 
    // 先调用一次get,求一次值
  }
  get () {
    pushTarget(this) // 这里Dep.target变为了这个userWatcher
    try {
      value = this.getter.call(vm, vm) 
      // 求值函数的调用
      // 如果观察的key是函数,就直接调用,如果不是就是上面parsePath的返回
    }
  }
}

parsePath

js
// util/lang.js
const bailRE = new RegExp(`[^${unicodeRegExp.source}.$_\\d]`) 
// /[^unicode.$_num]/  其中.是字符串,非转义,num代表数字
function parsePath (path) {
  if (bailRE.test(path)) {
    return;
  }
  const segments = path.split('.')
  // 返回的函数就是getter,调用时Dep.target是userWatcher
  // 调用到vm实例内的数据则会触发其get函数,收集依赖
  return function (obj) {
    for (let i = 0; i < segments.length; i++) {
      if (!obj) return
      obj = obj[segment[i]]
    }
    return obj
  }
}

当修改了watch的数据

修改了已经被watch 的数据,会触发watcher的update,最终执行watcher的run

js
// observer/watcher.js
run () {
  if (this.active) {
    const value = this.get() // getter函数触发,进行求值
    if (value !== this.value || isObject(value) || this.deep) {
      const oldValue = this.value
      this.value = value
      if (this.user) {
      	try {
          // cb就是定义好的watch回调
          this.cb.call(this.vm, value, oldValue)
        }
      }
    }
  }
}

深度遍历deep

watch 的定义中有个deep选项

js
// observer/watcher.js
get () {
  pushTarget(this)
  if (this.deep) {
    traverse(value) // 深度遍历
  }
  popTarget()
}

traverse

js
// observer/traverse.js
const seenObjects = new Set()
function traverse (val) {
  _traverse(val, seenObject)
  seenObject.clear()
}

function _traverse (val, seen) {
  let i, keys
  const isA = Array.isArray(val)
  if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {
    return // 非对象类型,或者已经冻结的对象值
  }
  if (val.__ob__) {
    // 有ob实例,被观测的
    const depId = val.__ob__.dep.id
    if (seen.has(depId)) {
      return;// 避免重复添加
    }
    seen.add(depId)
  }
  // 这样就能达到遍历了对象或者数组里面所有值的效果,遍历过程中就能触发他们的get,从而收集此时的userWatcher作为其订阅者,从而修改深层数据也能触发
  if (isA) {
    i = val.length;
    while (i--) _traverse(val[i], seen)
  } else {
    keys = Object.keys(val)
    i = keys.length
    while (i--) _traverse(val[keys[i]], seen)
  }
}