【VUE】— watch侦听器原理

系列文章目录

文章目录

  • 系列文章目录
  • 前言
  • 一、watch的运行时机
  • 二、源码分析
    • 2.1 initWatch
    • 2.2 createWatcher
    • 2.3 $watch
    • 2.4 watcher
  • 三、 深度监听
    • 3.1 traverse


前言

接下来一段时间深入学习一下vue相关的原理,加油。

一、watch的运行时机

beforeCreate之后,created之前,会initState,在initState中会调用initWatch

function initState(vm) {
    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 {
        observer(vm._data={}, true);
    }
    if (opts.computed) initMethods(vm, opts.computed);
    if (opts.watch && opts.watch !== nativeWatch) {
			// 初始化watch
			initWatch(vm, opts.watch);
		}
}

二、源码分析

2.1 initWatch

function initWatch (vm: Component, watch: Object) {
  // 1.遍历watch
  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 {
    // 2.创建watcher
      createWatcher(vm, key, handler)
    }
  }
}

这段代码只是简单的进行遍历,然后每个watch都使用createWater进行处理。

2.2 createWatcher

createWatcher函数接收四个参数,该函数内部其实就是从用户合起来传入的对象中把回调函数cb和参数options剥离出来,然后再以常规的方式调用$watch方法并将剥离出来的参数穿进去。

function createWatcher (
  vm: Component,
  expOrFn: string | Function,
  handler: any,
  options?: Object
) {
  //监听属性的值是一个对象,包含handler,deep,immediate
  if (isPlainObject(handler)) {
    options = handler
    handler = handler.handler
  }
  //如果回调函数是一个字符串,从VM中获取
  if (typeof handler === 'string') {
    handler = vm[handler]
  }
  //expOrFn是key, options是watch的全部选项
  return vm.$watch(expOrFn, handler, options)
}
  1. 获取到监听回调
  2. 调用vm.$watch

isPlainObject方法是判断handler是否为对象类型 Object;

//获取值得原始类型字符串
const _toString = Object.prototype.toString

export function isPlainObject (obj: any): boolean {
  return _toString.call(obj) === '[object Object]'
}

2.3 $watch

首先,会判断传入的回掉函数是不是一个对象,如果是,那么就表明用户是将第二个参数回掉函数和第三个参数options结合起来传入的,那么此时就调用createWatcher函数:

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
    // 每一个watch都配发一个watcher
    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}"`)
      }
    }
    return function unwatchFn () {
      watcher.teardown()
    }
  }

vm.$watch做了主要的两件事

  1. 为每个watch配发watcher;
  2. 根据immediate配置来判断是否立即执行监听回调

2.4 watcher

export default class Watcher {
  vm: Component;
  expression: string;
  cb: Function;
  id: number;
  deep: boolean;
  user: boolean;
  lazy: boolean;
  sync: boolean;
  dirty: boolean;
  active: boolean;
  deps: Array<Dep>;
  newDeps: Array<Dep>;
  depIds: SimpleSet;
  newDepIds: SimpleSet;
  before: ?Function;
  getter: Function;
  value: any;

  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.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()
    this.expression = process.env.NODE_ENV !== 'production'
      ? expOrFn.toString()
      : ''
    // parse expression for getter
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
        this.getter = noop
        process.env.NODE_ENV !== 'production' && warn(
          `Failed watching path: "${expOrFn}" ` +
          'Watcher only accepts simple dot-delimited paths. ' +
          'For full control, use a function instead.',
          vm
        )
      }
    }
    this.value = this.lazy
      ? undefined
      : this.get()
  }
  ...
  //此处只拿出了Watch类的构造方法;如需查看完整代码还请自行查阅
  }

新建watcher的时候主要做了以下几件事情:

  1. 监听的 key
  2. 监听回调 (watch中的cb
  3. 监听配置的options

三、 深度监听

当选项参数options中的deep属性为true时,如何实现深度观察呢?

所谓深度观察,就是当obj对象发生变化时我们会得到通知,通知当obj.a属性发生变化时我们也要能得到通知,简单的说就是观察对象内部值的变化。

要实现这个功能也不难,我们知道,要想让数据变化时通知我们,那我们只需成为这个数据的依赖即可,因为数据变化时会通知它所有的依赖,那么如何成为数据的依赖呢,很简单,读取一下数据即可。也就是说我们只需在创建watcher实例的时候把obj对象内部所有的值都递归的读一遍,那么这个watcher实例就会被加入到对象内所有值的依赖列表中,之后当对象内任意某个值发生变化时就能够得到通知了

在创建watcher实例的时候,会执行Watcher类中get方法来读取一下被观察的数据。

export default class Watcher {
    constructor (/* ... */) {
        // ...
        this.value = this.get()
    }
    get () {
        // ...
        // "touch" every property so they are all tracked as
        // dependencies for deep watching
        if (this.deep) {
            traverse(value)
        }
        return value
    }
}

get方法中,如果传入的deep===true,则会调用traverse函数。

3.1 traverse

const seenObjects = new Set()
 
export function traverse (val: any) {
    _traverse(val, seenObjects)
    seenObjects.clear()
}
 
function _traverse (val: any, seen: SimpleSet) {
    let i, keys
    const isA = Array.isArray(val)
    if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {
        return
    }
    if (val.__ob__) {
        const depId = val.__ob__.dep.id
        if (seen.has(depId)) {
            return
        }
        seen.add(depId)
    }
   
    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)
    }
}

可以看到,该函数其实就是个递归遍历的过程,把被观察数据的内部值都递归遍历读取一遍。

首先先判断传入的val类型,如果它不是Array或object,再或者已经被冻结,那么直接返回,退出程序。

然后拿到val的dep.id,存入创建好的集合seen中,因为集合相比数据而言它有天然的去重效果,以此来保证存入的dep.id没有重复,不会造成重复收集依赖。

接下来判断如果是数组,则循环数组,将数组中每一项递归调用_traverse;如果是对象,则取出对象所有的key,然后执行读取操作,再递归内部值。

这样,把被观察数据内部所有的值都递归的读取一遍后,那么这个watcher实例就会被加入到对象内所有值的依赖列表中,之后当对象内任意某个值发生变化时就能够得到通知了。

总结一下,其实traverse方法很好理解,主要做的事情是:

  1. 通过读取,就可以让这个属性收集到 watch-watcher
  2. 深层级的对象,其中的每个属性也都是响应式的,每个属性都有自己的依赖收集器,通过不断深入的读取每个属性,这样每个属性就都可以收集到 watch-watcher 了;这样不管对象内多深的属性变化,都会通知到 watch-watcher,于是这样就完成了深度监听;
    update

你可能感兴趣的:(vue,vue,watch源码分析)