vue中的watch实现原理

313 阅读1分钟

前言

watch的实现原理同样是利用了watcher。不同的是:渲染watch在数据更新时去更新dom元素,而watch在数据更新时执行相对应的操作。

watch实现原理

1.先获取传入的watch,并通过createWatcher创建

function initWatch(vm) {
  let watch = vm.$options.watch
  for(let key in watch) {
    const handler = watch[key]; // handler可能是数组、字符串、对象、函数
    if (Array.isArray(handler)) {
      handler.forEach(handler => {
        createWatcher(vm,key,handler)
      })
    } else {
      createWatcher(vm,key,handler);
    }
  }
}

2.在createWatcher中把handler函数提取处理,成为最终数据更新后的回调函数。

export function createWatcher(vm,exprOrFn,handler,options={}) {// options用来表示用户传入
  if (typeof handler == 'object') {
    options = handler
    handler = handler.handler
  } else if (typeof handler == 'string') {
    handler = vm[handler]
  }
  return vm.$watch(exprOrFn,handler,options)
}

3.创建一个Watcher实例,实现依赖收集和依赖更新操作。

export function stateMixin(Vue) {
  Vue.prototype.$nextTick = function (cb) {
    nextTick(cb)
  }
  Vue.prototype.$watch = function (exprOrFn,cb,options) {
    // 数据更新后应该让watcher重新执行
    let watcher = new Watcher(this,exprOrFn,cb,{...options,user:true})
    if (options.immediate) {
      cb() // 如果是immediate应该立刻执行
    }
  }
}

4.在watcher里,很重要的一步是获取需要监听的key值,通过getter函数可以获取到监听的值。

if (typeof exprOrFn == 'function') {
      this.getter = exprOrFn
    }else {
      this.getter = function() {
        // 去当前实例上取值
        let path = exprOrFn.split('.')
        let obj = vm;
        for (let i = 0; i < path.length; i++) {
          obj = obj[path[i]];
        }
        return obj;
      }
    }

5.在函数run中,如果是渲染watcher,只要调用get函数实现渲染就可以了。如果包含watch,则还需要调用用户传入的回调函数。

run() {
    let newValue = this.get() // 渲染
    let oldValue = this.value
    this.value = newValue // 更新老值
    if (this.user) {
      this.cb.call(this.vm,newValue,oldValue)
    };
  }