vue---计算属性(computed)

304 阅读4分钟

想通过写文章的方式记录下学习当中的知识点,也方便自己后期回顾,主要知识点来源为王红元老师的vue2教学视频以及各大社区搜索的知识点和各大博主的文章汇总。

什么是计算属性

所谓的计算属性,其实就是对data中的数据进行转化后再显示,或者将多个数据进行结合再显示,依旧可以使用插值语法渲染到结构中。

计算属性的作用

1)减少模板中的计算逻辑,一方面可以简化模板,另一方面也使逻辑更加清晰

2)内部对数据起到一个缓存的作用,当数据没有变化时,计算属性不执行,直接显示缓存的结果,性能更高

计算属性的写法

两种定义方式:方法 和set/get

    // 完整版写法
 computed: {
    // 计算属性 一般是没有set方法的 ,只有get方法,只读属性
    fullName: {
      get() {
        return this.firstName + '' + this.lastName
      },
      Set() {}
    }
  }
//简易写法
computed(){
  fullName(){
    return this.firstName+" "+this.lastName
	}
}

每个计算属性中都有gettersetter。(响应式原理中详细记录)

通过getter我们可以获取对象中相关属性的值。

通过setter我们可以修改对象中的相关属性。

计算属性与methods的对比

1.计算属性会进行缓存(懒执行),多次使用也只调用一次,methods不会进行缓存,多次使用则多次调用

2.methods是函数调用,computed是属性调用

3.computed定义的方法是以属性访问的形式调用的,methods定义的方法是以函数访问的形式调用的

4.computed是基于响应式依赖进行缓存的,只有响应式依赖发生改变时,才会重新求值,对于任何复杂逻辑,都应当使用计算属性

计算属性与watch的对比

1.computed是计算属性;watch的功能是监听一个值的变化然后执行对应的回调函数

2.computed是懒执行,只有当所依赖的属性发生变化的时候才会执行,没有变化则一直调用缓存,而watch每次监听每次都会调用回调函数,没有缓存

3.computed不能支持异步,而watch可以支持异步

计算属性的原理

注:摘抄自山月老师的前端每日一题

computed原理,首先得讲 vue 响应式原理,因为 computed 的实现是基于 Watcher 对象的。 那么 vue 的响应式原理是什么呢,众所周知,vue2 是基于 Object.defineProperty 实现监听的。在 vue 初始化数据 data 和 computed 数据过程中。会涉及到以下几个对象:

  1. Observe 对象
  2. Dep 对象
  3. Watch 对象

Observe 对象是在 data 执行响应式时候调用,因为 computed 属性基于响应式属性,所以其不需要创建 Observe 对象。 Dep 对象主要功能是做依赖收集,有个属性维护多个Watch对象,当更新时候循环调用每个 Watch 执行更新。Watch 对象主要是用于更新,而且是收集的重点对象。

这里谈到 computed 计算属性,首先要知道,其有两种定义方式,一种是 方法,另一种是 get,set 属性。而且,其内部监听的对象必须是已经定义响应式的属性,比如 data 的属性 vuex 的属性。

vue 在创建 computed 属性时候,会循环所有计算属性,每一个计算属性会创建一个 watch,并且在通过 defineProperty 定义监听,在 get 中,计算属性工作是做依赖收集,在 set 中,计算属性重要工作是重新执行计算方法,这里需要多补充一句,因为 computed 是懒执行,也就是说第一次初始化之后,变不会执行计算,下一次变更执行重新计算是在 set 中。

另一个补充点是依赖收集的时机,computed 收集时机和 data 一样,是在组件挂载前,但是其收集对象是自己属性对应的 watch,而 data 本身所有数据对应一个 watch。

以下附计算属性源码验证说法:

function initComputed(vm: Component, computed: Object) {
  // $flow-disable-line
  const watchers = (vm._computedWatchers = Object.create(null));
  // computed properties are just getters during SSR
  const isSSR = isServerRendering();

  for (const key in computed) {
    const userDef = computed[key];
    const getter = typeof userDef === "function" ? userDef : userDef.get;
    if (process.env.NODE_ENV !== "production" && getter == null) {
      warn(`Getter is missing for computed property "${key}".`, vm);
    }

    if (!isSSR) {
      // create internal watcher for the computed property.
      watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      );
    }

    // component-defined computed properties are already defined on the
    // component prototype. We only need to define computed properties defined
    // at instantiation here.
    if (!(key in vm)) {
      defineComputed(vm, key, userDef);
    } else if (process.env.NODE_ENV !== "production") {
      if (key in vm.$data) {
        warn(`The computed property "${key}" is already defined in data.`, vm);
      } else if (vm.$options.props && key in vm.$options.props) {
        warn(
          `The computed property "${key}" is already defined as a prop.`,
          vm
        );
      }
    }
  }
}

可以看到,在执行 new Watcher 之前,会对计算属性做判断,判断其是否为函数,如果不是则取 getter。这是因为计算属性有两种定义方式。之后第二步是执行 deineCoumputed。这一步只是简单的调用defineProterty我就不贴代码了。

关于计算属性的 gettersetter 定义如下: 重点关注 get 的懒加载部分,和 Watcher 的定义

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

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