likes
comments
collection
share

vue2的computed和watche属性

作者站长头像
站长
· 阅读数 6

计算属性原理

  • 依赖值发生改变后才会重新执行用户的方法,需要维护一个dirty属性,默认不会立即执行
  • 计算属性是一个defineProperty,有gettersetter的方法;也是一个watcher,当计算属性被调用的时候会执行get方法,从而触发内部调用其他响应式属性的setter方法,从而使得响应式属性收集计算属性的watcher,这样还不够,内部调用的的响应式属性还需要收集计算属性所处组件的watcher,使得内部响应式属性调用setter后通知计算属性的watcher改变dirty属性,并让组件的watcher重新执行,当组件的render重新执行后又会去调用计算熟悉的get方法,由于dirty为true,会重新执行一下其get方法。
// 需要一个数组,存储当前组件的watcher,然后存储当前计算属性的watcher
Dep.target = null
const stack = []
export function pushTarget(watcher) {
    stack.push(watcher)
    Dep.target = watcher
}
export function popTarget() {
 stack.pop()
 Dep.target = stack.length > 0 ? stack[stack.length - 1] : null
}

// watcher
function initComputed(vm) {
  const computed = vm.$options.computed;
  const watcher = vm._computedWatchers = {}
  for (const key in computed) {
    let userDef = computed[key]
    const fn = typeof userDef === 'function' ? userDef : userDef.get
    
    watcher[key] = new Watcher(vm, fn, { lazy: true })
    defineComputed(vm, key, userDef)
  }
}

function defineComputed(target, key, userDef) {
  const getter = typeof userDef === 'function' ? userDef : userDef.get
  const setter = userDef.set || (() => {})
  
  Object.defineProperty(target, key, {
    get: createComputedGetter(getter, key),
    set: setter
  })
}

function createComputedGetter(getter) {
  return () => {
    const watcher = this._computedWatcher[key]
    if (watcher.dirty) { // 计算属性不会收集当前组件的watcher
      watcher.evalute()
    }
    if (Dep.target) { // 让计算属性中的所依赖的属性去收集组件的watcher
      watcher.depend()
    }
    return watcher.value
  }
}

class Watcher {
  constructor(vm, fn, options) {
    ...
    this.lazy = options.lazy
    this.dirty = this.lazy
    this.lazy ? undefined : this.get()
  }
  evalute() {
    this.value = this.get()
    this.dirty = false
  }
  update() {
    if (this.lazy) {
      this.dirty = true
    } else {
      queueWatcher(this)
    }
  }
  depend() {
    let i = this.deps.length
    while (i--) { // 这也就是为什么watcher身上需要记住哪些属性收集了自己,目的就是为了computed属性中依赖的属性去收集当前的组件的watcher
      this.deps[i].depend()
    }
  }
}

watch属性原理

  • watch也是基于Watcher的,watcher会立即执行,只需要让其立即执行一下监听属性的get即可让响应式数据收集当前用户自己的watcher,后续响应式数据发生改变去触发用户自己的callback
function initWatch(vm) {
  const watch = this.$options.watch
  for (const key in watch) {
    const handler = watch[key]
    if (Array.isArray(handler)) {
      for (let i = 0; i < handler.length; ++i) {
        createWatch(vm, key, handler[i])
      }
    } else {
      createWatch(vm, key, handler)
    }
  }
}

function createWatch(vm, key, handler) {
  if (typeof handler === 'string') handler = vm[handler]
  return vm.$watch(key, handler)
}

Vue.prototype.$watch = (exprOrFn, cb) => {
  new Watch(this, exprOrFn, { user: true }, cb) // 标志自己的watcher
  // watch会立即执行,会触发需要监听属性的`get`,从而使得该响应式属性收集用户的watcher
}

// watcher
class Watcher {
  constructor(vm, exprOrFn, options, cb) {
    if (typeof exprOrFn === 'string') {
      this.getter = function() {
        return vm[exprOrFn]
      }
    } else this.getter = exprOrFn
    
    this.user = options.user
    this.cb = cb
    this.value = this.lazy ? undefined : this.get()
  }
  run() {
    let oldValue = this.value
    this.value = this.get()
    if (this.user) {
      this.cb.call(this.vm, newvalue, oldvalue)
    }
  }
}