资讯专栏INFORMATION COLUMN

根据调试工具看Vue源码之computed(二)

U2FsdGVkX1x / 1700人阅读

摘要:回顾上回提到,计算属性的缓存与这个类的属性有关,那么这次我们接着来看下,属性到底取决于什么情况来变化,从而对进行缓存。

回顾
上回提到,computed————计算属性的缓存与Watcher这个类的dirty属性有关,那么这次我们接着来看下,dirty属性到底取决于什么情况来变化,从而对computed进行缓存。
依赖收集

切入正题之前,我们先来看一个问题:如果一个computed的结果是受data属性下的值影响的,那么如何去捕获因某个值变化而引起的computed的变化?答案是:依赖收集

根据上面的断点,在update函数执行之前,我们注意到,有个reactiveSetter函数在它之前。我们点击右侧调用栈中的reactiveSetter,此时有一个函数特别醒目:defineReactive$$1,经过又一次的断点,我们发现它在几处都有调用:

initRender函数中调用

walk函数中调用

在实际断点调试的时候,我们很容易可以知道存在这样的,同时也是与本文有关的调用顺序(从下往上):

defineReactive$$1

walk

Observer

observe

initData

initState

...

Observer

根据上边提供的调用顺序,我们重点看一下几个关键的函数:

observe
/**
 * Attempt to create an observer instance for a value,
 * returns the new observer if successfully observed,
 * or the existing observer if the value already has one.
 */
function observe (value, asRootData) {
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  var ob;
  if (hasOwn(value, "__ob__") && value.__ob__ instanceof Observer) {
    ob = value.__ob__;
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value);
  }
  if (asRootData && ob) {
    ob.vmCount++;
  }
  return ob
}

光看注释我们都能知道,observe函数的作用是:为某个值创建一个observer实例,随后将这个observer实例返回,在这里起到一个对值进行筛选的作用

Observer
/**
 * Observer class that is attached to each observed
 * object. Once attached, the observer converts the target
 * object"s property keys into getter/setters that
 * collect dependencies and dispatch updates.
 */
var Observer = function Observer (value) {
  this.value = value;
  this.dep = new Dep();
  this.vmCount = 0;
  def(value, "__ob__", this);
  if (Array.isArray(value)) {
    if (hasProto) {
      protoAugment(value, arrayMethods);
    } else {
      copyAugment(value, arrayMethods, arrayKeys);
    }
    this.observeArray(value);
  } else {
    this.walk(value);
  }
};

注释大意:

每个被观察的对象都附属于Observer类。每次对对象的观察都会将它的 gettersetter属性覆盖,用以收集依赖以及触发更新
walk && defineReactive$$1
Observer.prototype.walk = function walk (obj) {
  var keys = Object.keys(obj);
  for (var i = 0; i < keys.length; i++) {
    defineReactive$$1(obj, keys[i]);
  }
};

/**
 * Define a reactive property on an Object.
 */
function defineReactive$$1 (
  obj,
  key,
  val,
  customSetter,
  shallow
) {
  var dep = new Dep();

  var property = Object.getOwnPropertyDescriptor(obj, key);
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  var getter = property && property.get;
  var setter = property && property.set;
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key];
  }

  var childOb = !shallow && observe(val);
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      var value = getter ? getter.call(obj) : val;
      if (Dep.target) {
        dep.depend();
        if (childOb) {
          childOb.dep.depend();
          if (Array.isArray(value)) {
            dependArray(value);
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      var value = getter ? getter.call(obj) : val;
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== "production" && customSetter) {
        customSetter();
      }
      // #7981: for accessor properties without setter
      if (getter && !setter) { return }
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      childOb = !shallow && observe(newVal);
      dep.notify();
    }
  });
}

其中,这端代码是关键:

get: function reactiveGetter () {
      var value = getter ? getter.call(obj) : val;
      if (Dep.target) {
        dep.depend();
        if (childOb) {
          childOb.dep.depend();
          if (Array.isArray(value)) {
            dependArray(value);
          }
        }
      }
      return value
    },

如果阅读了整段defineReactive$$1函数,那么很容易就发现,dep不过是Depnew出来的实例,那么即使不看Dep.prototype.depend的实现,你也知道dep.depend()其实也就是在收集依赖。
另外,这段代码意味着单单在data属性下声明一个变量是不会进行依赖收集的,需要变量在程序中被调用,那么才会被收集到依赖中(其实这也是一种优化)

Dep类下的相关实现
/**
 * A dep is an observable that can have multiple
 * directives subscribing to it.
 */
var Dep = function Dep () {
  this.id = uid++;
  this.subs = [];
};

Dep.prototype.addSub = function addSub (sub) {
  this.subs.push(sub);
};

Dep.prototype.removeSub = function removeSub (sub) {
  remove(this.subs, sub);
};

Dep.prototype.depend = function depend () {
  if (Dep.target) {
    Dep.target.addDep(this);
  }
};

Dep.prototype.notify = function notify () {
  // stabilize the subscriber list first
  var subs = this.subs.slice();
  if (process.env.NODE_ENV !== "production" && !config.async) {
    // subs aren"t sorted in scheduler if not running async
    // we need to sort them now to make sure they fire in correct
    // order
    subs.sort(function (a, b) { return a.id - b.id; });
  }
  for (var i = 0, l = subs.length; i < l; i++) {
    subs[i].update();
  }
};
总结

上面说了这么多未免有点乱,最后重新梳理下computed实现缓存的思路:

Vue在初始化data属性时,会将data属性下相关的变量进行观察observe),同时重新设置它的gettersetter属性,以便在其被调用时收集到它的依赖。

初始化computed

调用computed时判断this.dirty属性,为true时调用evaluate重新计算它的值并将this.dirty置为false,将值存在this.value 上,再调用computed则直接返回this.value

computed中依赖的值发生变化时会自动触发该值的setter属性,紧接着调用notify函数,遍历一个subs数组,触发update函数将this.dirty重置为true

computed再次被调用时,由于this.dirty已经是true,则会重新计算

扫描下方的二维码或搜索「tony老师的前端补习班」关注我的微信公众号,那么就可以第一时间收到我的最新文章。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/102605.html

相关文章

  • 根据调试工具Vue源码computed(一)

    摘要:官方定义类型详细计算属性将被混入到实例中。所有和的上下文自动地绑定为实例计算属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算。注意,如果某个依赖比如非响应式属性在该实例范畴之外,则计算属性是不会被更新的。 官方定义 类型:{ [key: string]: Function | { get: Function, set: Function } } 详细:计算属性将被混入到 V...

    aisuhua 评论0 收藏0
  • 根据调试工具Vue源码虚拟dom(

    摘要:前言上回我们提到,在子组件存在的情况下,父组件在执行完钩子函数之后生成子组件的实例,子组件执行钩子函数,同时也检查是否也有子组件,有则重复父组件的步骤,否则子组件的元素渲染深入了解在上一篇文章中其实我们提到一个函数 前言 上回我们提到,在子组件存在的情况下,父组件在执行完created钩子函数之后生成子组件的实例,子组件执行created钩子函数,同时也检查是否也有子组件,有则重复父组...

    zzir 评论0 收藏0
  • 前方来报,八月最新资讯--关于vue2&3的最佳文章推荐

    摘要:哪吒别人的看法都是狗屁,你是谁只有你自己说了才算,这是爹教我的道理。哪吒去他个鸟命我命由我,不由天是魔是仙,我自己决定哪吒白白搭上一条人命,你傻不傻敖丙不傻谁和你做朋友太乙真人人是否能够改变命运,我不晓得。我只晓得,不认命是哪吒的命。 showImg(https://segmentfault.com/img/bVbwiGL?w=900&h=378); 出处 查看github最新的Vue...

    izhuhaodev 评论0 收藏0
  • 根据调试工具Vue源码生命周期(一)

    摘要:由于工作中经常使用调试工具来定位问题,觉着这东西真的挺好用。突然有一天受到启发,想着我学习源码是否也可以通过调试工具呢因此,诞生了这篇文章来记录我的一些学习成果,后续应该会写成一个系列。 由于工作中经常使用chrome调试工具来定位问题,觉着这东西真的挺好用。突然有一天受到启发,想着:我学习源码是否也可以通过调试工具呢? 因此,诞生了这篇文章来记录我的一些学习成果,后续应该会写成一个...

    My_Oh_My 评论0 收藏0
  • 根据调试工具Vue源码组件通信(一)

    摘要:根据调试工具看源码之组件通信一根据调试工具看源码之组件通信一在平时的业务开发中,相信在座的各位没少用过组件通信。看完本文可以帮助你了解组件的通信方式及原理,从而进一步加深对的理解,远离工程师的行列。 根据调试工具看Vue源码之组件通信(一)## 根据调试工具看Vue源码之组件通信(一) 在平时的业务开发中,相信在座的各位没少用过组件通信。然而,对于一些新手/业务熟手来说,不懂技术原理往...

    付伦 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<