computed实现原理

Vue响应系统

在这里插入图片描述
vue会遍历 data 中的对象,通过使用 Object.defineProperty 将这些属性全部转为 getter/setter,在属性被访问和修改时通知变化,每个组件都有相应的 watcher 实例对象,他会在组件渲染过程中把属性记录,当属性的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件更新。

vue响应系统,核心三点:observe,watcher,dep:
observe:遍历 data 中的属性,使用 Object.defineProperty 的 get/set 方法对其进行数据劫持
dep:每个属性拥有自己的消息订阅dep,用于存放所有订阅了该属性的观察者对象
watcher:观察者通过dep实现随响应属性的监听,监听到结果后,主动触发自己的回调进行响应

initState函数

// 计算属性的初始化是在 initState 函数中完成的,这个方法就是初始化 props,data,methods,watch,computed 等属性。
export function initState(vm: Component) {
	  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 {
	    observe(vm._data = {}, true /* asRootData */)
	  }
	  // computed初始化
	  // 调用了initComputed函数,传入了两个参数vm实例和opt.computed开发者定义的computed选项
	  if (opts.computed) initComputed(vm, opts.computed)
	  if (opts.watch && opts.watch !== nativeWatch) {
	    initWatch(vm, opts.watch)
	  }
}

初始化Computed

// 初始化计算属性
function initComputed (vm: Component, computed: Object) {
  ...
  // 遍历 computed 计算属性
  for (const key in computed) {
    ...
    // 创建 Watcher 实例
    // 四个参数:vm 实例、getter求值函数、noop 空函数、computedWatcherOptions 常量对象(在这里提供给 Watcher 一个标识 {computed:true} 项,表明这是一个计算属性而不是非计算属性的观察者,watchers为vm._computedWatchers对象的引用
    watchers[key] = new Watcher(vm, getter || noop, noop, computedWatcherOptions)

    // 创建属性 vm.reversedMessage,并将提供的函数将用作属性 vm.reversedMessage 的 getter,
    // 最终 computed 与 data 会一起混合到 vm 下,所以当 computed 与 data 存在重名属性时会抛出警告
    defineComputed(vm, key, userDef)
    ...
  }
}

export function defineComputed (target: any, key: string, userDef: Object | Function) {
  ...
  // 创建 get set 方法
  sharedPropertyDefinition.get = createComputedGetter(key)
  sharedPropertyDefinition.set = noop
  ...
  // 创建属性 vm.reversedMessage,并初始化 getter setter
  Object.defineProperty(target, key, sharedPropertyDefinition)
}
function createComputedGetter (key) {
  return function computedGetter () {
    // 通过计算属性key拿到对应的watcher,并启动观察者进行观察
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      // dirty标志数据是否发生变化
      if (watcher.dirty) {
        // 执行watcher.get()方法
        watcher.evaluate()
      }
      // 判断是否处于依赖收集状态
      if (Dep.target) {
        watcher.depend()
      }
      return watcher.value
    }
  }
}

watcher

/**
* watchers[key] = new Watcher(
* vm,  vm实例
* getter || noop,  getter求值函数
* noop,             noop空函数
* computedWatcherOptions   配置对象:{computed:true},表明这是一个计算属性的观察者
) */
class Watcher {
  constructor(
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    if (options) {
      this.computed = !!options.computed
    }
    //根据{computed:true} 创建Dep实例,该属性的消息订阅器
    if (this.computed) {
      this.value = undefined
      this.dep = new Dep()
    } else {
      this.value = this.get()
    }
  }

  get() {
    // this指的是watcher实例,将当前watcher实例暂存到Dep.target,表示开启依赖收集任务
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      // 执行过程中可以手机到vm.reversedMesage的依赖
      value = this.getter.call(vm, vm)
    } catch (e) {

    } finally {
      // 结束依赖收集任务
      popTarget()
    }
    return value
  }

  update() {
    if (this.computed) {
      if (this.dep.subs.length === 0) {
        this.dirty = true
      } else {
        this.getAndInvoke(() => {
          this.dep.notify()
        })
      }
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this)
    }
  }

  evaluate() {
    // dirty标志数据是否发生变化
    if (this.dirty) {
      this.value = this.get()
      this.dirty = false
    }
    return this.value
  }

  depend() {
    if (this.dep && Dep.target) {
      this.dep.depend()
    }
  }
}

Dep

// dep对象中两个属性,存储id

class Dep {
  static target: ?Watcher;
  subs: Array<Watcher>;

  constructor() {
    //标识当前Dep id
    this.id = uid++
    // 存放所有的监听watcher
    this.subs = []
  }
  // 添加一个观察者对象
  addSub(sub: Watcher) {
    this.subs.push(sub)
  }
  // Dep.target作用:只有需要的时候才会收集依赖
  depend() {
    if (Dep.target) {

      Dep.target.addDep(this)
    }
  }
  // 调用watcher更新
  notify() {
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      // update是给依赖变化时使用的,包含对watch的处理
      subs[i].update()
    }
  }
}
Dep.target = null
const targetStack = []

export function pushTarget(_target: Watcher) {
  // 在一次依赖收集期间,如果有其他依赖收集任务开始(比如:当前 computed 计算属性嵌套其他 computed 计算属性),
  // 那么将会把当前 target 暂存到 targetStack,先进行其他 target 的依赖收集,
  if (Dep.target) targetStack.push(Dep.target)
  Dep.target = _target
}

export function popTarget() {
  // 当嵌套的依赖收集任务完成后,将 target 恢复为上一层的 Watcher,并继续做依赖收集
  Dep.target = targetStack.pop()
}



watcher中实例化dep并向dep.subs中添加了订阅者,dep通过notify方法遍历dep.subs通知每个watcher更新

总结

  1. 当组件初始化的时候,computed和data会分别创建各自的响应系统,Observer遍历data中每个属性设置get/set数据拦截

  2. 初始化computed调用initComputed函数

    1. 注册一个watcher实例,并实例化一个Dep消息订阅器用作后续猴急依赖(比如渲染函数的watcher或者其他观察该计算属性变化的watcher)
    2. 调用计算属性会触发Object.defineProperty的get访问器函数
    3. 调用watcher.depend()方法向自身的消息订阅dep的subs中添加其他属性的watcher
    4. 调用watcher的evaluate方法(进而调用watcher的get方法)让自身成为其他watcher的消息订阅器的订阅者,首先将watcher赋给Dep.target,然后执行getter求值函数,当访问求值函数里面的属性,同样触发他们的get访问器函数从而将该计算属性的watcher添加到求值函数中属性的watcher的消息订阅器dep中,当这些操作完成后,最后关闭Dep.target赋给null并返回求值函数结果
  3. 当某个属性发生变化,触发set拦截函数,然后调用自身消息订阅器dep的notify方法,遍历当前dep中保存着所有订阅者watcher的subs数组,并逐个调用watcher的update方法,完成响应更新。

相关推荐
©️2020 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页