computed的原理

251 阅读3分钟

计算属性默认不执行,只有当取值的时候才执行

多次取值如果依赖的值不变化,就不会执行

function initComputed(vm, computed) {
  // 将 每个计算属性的watchers存起来挂到vm上
  const watchers = vm._computedWatchers = {}
  for (const key in computed) {
    const userDef = computed[key]
    // 依赖的属性变化就重新取值
    let getter = typeof userDef == 'function' ? userDef : userDef.get

    // 每个计算属性本质都是watcher 目前还没用
    watchers[key] =  new Watcher(vm, getter, () => { }, { lazy: true })

    // 将key定义在vm上 
    defineComputed(vm, key, userDef)
  }
}

function createdComputedGetted(key) {
  return function () {
    let watcher =  this._computedWatchers[key];
    if(watcher.dirty){
      watcher.evaluate();
    }
    return watcher.value
  }
  
}

function defineComputed(vm, key, userDef) {
  let sharedProperty = {}
  if (typeof userDef == 'function') {
    sharedProperty.get = userDef
  }
  else {
    sharedProperty.get = createdComputedGetted(key)
    sharedProperty.set = userDef.set || function () {}
    // sharedProperty.set = userDef.set || ()=>()
  }
  Object.defineProperty(vm, key, sharedProperty)
}


watcher.js文件
  // 计算属性走的getter方法
  evaluate(){
    // 将数据标为不是脏的
    this.dirty = false
    this.value = this.getters()
  }

  //当渲染时自动会执行渲染
  getters() {
    //取值之前先把自己挂载到dep上的静态属性,类似于全局后面变量取值时会将dep和watcher关联
    pushTarget(this);
    const value = this.exprFunc.call(this.vm);
    popTarget();
    return value
  }

将 每个计算属性的watchers存起来挂到vm上

定义一个dirty标识取得数据是否是脏的,默认为脏的,重写getter方法,

取值时,找到key对应的watcher,判断watcher的dirty是否为true ,

如果是则调用watcher的ecaluate方法,exprFunc方法必须指向当前的vm要不this指向当前watcher,

这样会造成一个问题,这个数据永远都不是脏的了,

在改变属性时,

<li>{{fullname}}</li>
    fullname:{
          get(){
            console.log(111);
            return this.age + this.name
          }
        }
      }

这句话的意思是,fullname不会收集渲染watcher 的属性,fullname没有dep手机功能,取值直接去vm上取值, fullname是计算属性的话,方法里面以来的属性会收集计算属性的watcher,

计算属性中的值应该记录计算属性的watcher和渲染watcher

  // 判断是否还有Dep.Target
    if(Dep.target){
      // 如果有将当前watcher对应的deps 在收集当前Dep.target)
      watcher.depend()
    }

在取值的时候将当前watcher里面对应的deps循环进行收集当前Dep.target)

image.png

computed的原理

  1. 初始化computed的时候,get和set相当于object.defineproprety的get,set,循环遍历computed,并且new watcher,并且将watcher实例存到vm._componentsWatcher[key]对象中,同时有个lazy属性标识watcher内部不默认执行get函数,每个watcher内部会有dity标识,标识数据是不是脏的。

  2. 将key代理到vm上面,改写劫持的getter函数,获取对应的watcher,判断wathcer.dirty为真,表明数据是脏的,则调用watcher里面的取值方法,取完值将依赖的属性分别收集当前计算属性watcher,当前计算属性watcher收取依赖的属性,取完值之后将状态改为false,这样会造成数据更新了可视图还是不会变,因为计算属性依赖的值没有收集渲染watcher ,所以将当前计算属性watcher里面的deps数组进行循环收集渲染watcher,这样当依赖的属性变了,视图才会更新,重新执行render方法。

  3. 渲染watcher会在计算属性wathcer之前执行,render执行如果要取计算属性的值,则会先走到改写的getter方法, 取到对应的watcher,然后进行第二部,

  4. 当依赖的属性变了之后会走run方法,判断如果lazy为true为计算属性的话,会将dity变为true标识数据已经脏了,但是不执行get方法,除了watcher为计算属性的话都执行。

这里比较绕,也是最难的,

这里实现dep对应多个watcher的情况