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

栏目: 编程语言 · 发布时间: 5年前

内容简介:计算属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算。注意,如果某个依赖 (比如非响应式属性) 在该实例范畴之外,则计算属性是不会被更新的。上面这几段话其实可以归纳为以下几点:同以往一样,先新建一个

官方定义

{ [key: string]: Function | { get: Function, set: Function } }

计算属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算。注意,如果某个依赖 (比如非响应式属性) 在该实例范畴之外,则计算属性是不会被更新的。

上面这几段话其实可以归纳为以下几点:

  • computed 是计算属性,会被混入到 Vue 实例中
  • computed 的结果会被 缓存 ,除非依赖的响应式属性变化才会 重新计算

如何初始化 computed

同以往一样,先新建一个 Vue 项目,同时加入以下代码:

export default {
  name: 'test',
  data () {
    return {
      app: 666
    }
  },
  created () {
    console.log('app proxy -->', this.appProxy)
  },
  computed () {
    appProxy () {
      debugger
      return this.app
    }
  }
}

F12 打开调试界面,刷新后断点停在了 debugger 的位置,同时可以看到右边的调用栈:

appProxy
get
evaluate
computedGetter
created

瞥到 computedGetter 之后,点进去,可以看到:

function createComputedGetter (key) {
  return function computedGetter () {
    var watcher = this._computedWatchers && this._computedWatchers[key];
    if (watcher) {
      if (watcher.dirty) {
        watcher.evaluate();
      }
      if (Dep.target) {
        watcher.depend();
      }
      return watcher.value
    }
  }
}

看到这里不禁一脸懵逼:grimacing:

当然,根据前面我们看源码的经验,没有思路时,直接搜索相关函数的调用位置,这里我们可以直接搜索 createComputedGetter ,看看它是在哪里调用的。此处忽略搜索的过程,直接给出我的结论:

Vue 中存在两种初始化 computed 的方法:

option
Vue.prototype.extend

这两种初始化其实大同小异,我们选择在组件中写 computed ,自然断点就会跑到 Vue.prototype.extend 函数里:

...
if (Sub.options.computed) {
  initComputed$1(Sub);
}
...

initComputed$1 函数:

function initComputed$1 (Comp) {
  // 拿到组件的computed
  var computed = Comp.options.computed;
  for (var key in computed) {
    // 循环遍历
    defineComputed(Comp.prototype, key, computed[key]);
  }
}

显然,这句代码: defineComputed(Comp.prototype, key, computed[key])computed 挂载在了组件的原型上,下面来看下它的实现方式:

defineComputed

function defineComputed (
  target,
  key,
  userDef
) {
  // 判断是否要将结果缓存下来
  var shouldCache = !isServerRendering();
  // 下面进行分类判断
  // 对应的computed是函数的情况
  if (typeof userDef === 'function') {
    sharedPropertyDefinition.get = shouldCache
      ? createComputedGetter(key)
      : createGetterInvoker(userDef);
    sharedPropertyDefinition.set = noop;
  } else {
    // 非函数的情况
    sharedPropertyDefinition.get = userDef.get
      ? shouldCache && userDef.cache !== false
        ? createComputedGetter(key)
        : createGetterInvoker(userDef.get)
      : noop;
    sharedPropertyDefinition.set = userDef.set || noop;
  }
  if (process.env.NODE_ENV !== 'production' &&
      sharedPropertyDefinition.set === noop) {
    sharedPropertyDefinition.set = function () {
      warn(
        ("Computed property \"" + key + "\" was assigned to but it has no setter."),
        this
      );
    };
  }
  // 将sharedPropertyDefinition绑定到组件对象上
  Object.defineProperty(target, key, sharedPropertyDefinition);
}

:sweat_smile:感觉有点乱,最后再梳理下上边的逻辑:

initComputed

  • 执行 initComputed ,从 Vue 中拿到 computed 对象里所有的 key
  • 循环拿到的 key 值,调用 defineComputed 函数,把 computed 绑定到组件对象上

defineComputed

  • 判断是否在服务端渲染,是则 computed 的结果会被缓存,不是则不会缓存计算结果
  • 由于 computed 存在两种写法,这里也对 函数对象 的写法做了区分

computed 的结果缓存是如何实现的?

上面我们大致梳理了下 computed 的初始化逻辑,现在我们回过头来再看一下官方定义,发现其中提到了 计算属性会将计算结果缓存下来 ,那么这个计算结果到底是怎么被缓存下来的呢?

回到 defineComputed

defineComputed 里最后将 sharedPropertyDefinition 绑定到组件对象上,在代码里面可以看到对 sharedPropertyDefinition.get 做了特殊处理,两种情况分别封装了:

createComputedGetter
createGetterInvoker

createComputedGetter 的实现:

function createComputedGetter (key) {
  return function computedGetter () {
    var watcher = this._computedWatchers && this._computedWatchers[key];
    if (watcher) {
      if (watcher.dirty) {
        watcher.evaluate();
      }
      if (Dep.target) {
        watcher.depend();
      }
      return watcher.value
    }
  }
}

createGetterInvoker 的实现:

function createGetterInvoker(fn) {
  return function computedGetter () {
    return fn.call(this, this)
  }
}

可以看到,服务端渲染确实是对计算属性的结果不做缓存的,但是我们对结果是如何缓存,依旧是一脸懵逼:neutral_face:

回到最初的断点

刷新页面回到一开始我们在 appProxy 中打下的断点,在调用栈中有两个显眼的函数:

evaluate
get

分别点进去,我们可以看到:

evaluate 实现源码:

Watcher.prototype.evaluate = function evaluate () {
  this.value = this.get();
  this.dirty = false;
};

get 实现源码:

Watcher.prototype.get = function get () {
  pushTarget(this);
  var value;
  var vm = this.vm;
  try {
    value = this.getter.call(vm, vm);
  } catch (e) {
    if (this.user) {
      handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));
    } else {
      throw e
    }
  } finally {
    // "touch" every property so they are all tracked as
    // dependencies for deep watching
    if (this.deep) {
      traverse(value);
    }
    popTarget();
    this.cleanupDeps();
  }
  return value
};

结合上面给出的 createComputedGetter 源码我们可以知道, computed 的计算结果是通过 Watcher.prototype.get 来得到的,拿到 value 以后,在 Wathcer.prototype.evaluate 中执行了这样一行代码:

...
this.dirty = false;

聪明的读者肯定猜到了,计算属性是否重新计算结果,肯定跟这个属性有关。接下来我们只要跟踪这个属性的变化,就可以轻松的知道计算属性的缓存原理了。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Python 3网络爬虫开发实战

Python 3网络爬虫开发实战

崔庆才 / 人民邮电出版社 / 2018-4 / 99

本书介绍了如何利用Python 3开发网络爬虫,书中首先介绍了环境配置和基础知识,然后讨论了urllib、requests、正则表达式、Beautiful Soup、XPath、pyquery、数据存储、Ajax数据爬取等内容,接着通过多个案例介绍了不同场景下如何实现数据爬取,后介绍了pyspider框架、Scrapy框架和分布式爬虫。 本书适合Python程序员阅读。一起来看看 《Python 3网络爬虫开发实战》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

在线进制转换器
在线进制转换器

各进制数互转换器

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换