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

详细:

计算属性将被混入到 Vue 实例中。所有 gettersetterthis 上下文自动地绑定为 Vue 实例。

注意如果你为一个计算属性使用了箭头函数,则 this 不会指向这个组件的实例,不过你仍然可以将其实例作为函数的第一个参数来访问。

computed: {
  aDouble: vm => vm.a * 2
}

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

示例:

var vm = new Vue({
  data: { a: 1 },
  computed: {
    // 仅读取
    aDouble: function () {
      return this.a * 2
    },
    // 读取和设置
    aPlus: {
      get: function () {
        return this.a + 1
      },
      set: function (v) {
        this.a = v - 1
      }
    }
  }
})
vm.aPlus   // => 2
vm.aPlus = 3
vm.a       // => 2
vm.aDouble // => 4

模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。例如:

<div id="example">
  {{ message.split('').reverse().join('') }}
</div>

在这个地方,模板不再是简单的声明式逻辑。你必须看一段时间才能意识到,这里是想要显示变量 message 的翻转字符串。当你想要在模板中的多处包含此翻转字符串时,就会更加难以处理。

所以,对于任何复杂逻辑,你都应当使用计算属性。

<div id="example">
  <p>Original message: "{{ message }}"</p>
  <p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>
var vm = new Vue({
  el: '#example',
  data: {
    message: 'Hello'
  },
  computed: {
    // 计算属性的 getter
    reversedMessage: function () {
      // `this` 指向 vm 实例
      return this.message.split('').reverse().join('')
    }
  }
})

结果:

Original message: "Hello"

Computed reversed message: "olleH"

这里我们声明了一个计算属性 reversedMessage。我们提供的函数将用作 property vm.reversedMessagegetter 函数:

console.log(vm.reversedMessage) // => 'olleH'
vm.message = 'Goodbye'
console.log(vm.reversedMessage) // => 'eybdooG'

你可以打开浏览器的控制台,自行修改例子中的 vm。vm.reversedMessage 的值始终取决于 vm.message 的值。

你可以像绑定普通 property 一样在模板中绑定计算属性。Vue 知道 vm.reversedMessage 依赖于 vm.message,因此当 vm.message 发生改变时,所有依赖 vm.reversedMessage 的绑定也会更新。而且最妙的是我们已经以声明的方式创建了这种依赖关系:计算属性的 getter 函数是没有副作用 (side effect) 的,这使它更易于测试和理解。

计算属性缓存 vs 方法

你可能已经注意到我们可以通过在表达式中调用方法来达到同样的效果:

<p>Reversed message: "{{ reversedMessage() }}"</p>
// 在组件中
methods: {
  reversedMessage: function () {
    return this.message.split('').reverse().join('')
  }
}

我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要 message 还没有发生改变,多次访问 reversedMessage 计算属性会立即返回之前的计算结果,而不必再次执行函数。

这也同样意味着下面的计算属性将不再更新,因为 Date.now() 不是响应式依赖:

computed: {
  now: function () {
    return Date.now()
  }
}

相比之下,每当触发重新渲染时,调用方法将总会再次执行函数。

我们为什么需要缓存?假设我们有一个性能开销比较大的计算属性 A,它需要遍历一个巨大的数组并做大量的计算。然后我们可能有其他的计算属性依赖于 A。如果没有缓存,我们将不可避免的多次执行 A 的 getter!如果你不希望有缓存,请用方法来替代。

计算属性 vs 侦听属性

Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动:侦听属性。当你有一些数据需要随着其它数据变动而变动时,你很容易滥用 watch——特别是如果你之前使用过 AngularJS。然而,通常更好的做法是使用计算属性而不是命令式的 watch 回调。细想一下这个例子:

<div id="demo">{{ fullName }}</div>
var vm = new Vue({
  el: '#demo',
  data: {
    firstName: 'Foo',
    lastName: 'Bar',
    fullName: 'Foo Bar'
  },
  watch: {
    firstName: function (val) {
      this.fullName = val + ' ' + this.lastName
    },
    lastName: function (val) {
      this.fullName = this.firstName + ' ' + val
    }
  }
})

上面代码是命令式且重复的。将它与计算属性的版本进行比较:

var vm = new Vue({
  el: '#demo',
  data: {
    firstName: 'Foo',
    lastName: 'Bar'
  },
  computed: {
    fullName: function () {
      return this.firstName + ' ' + this.lastName
    }
  }
})

计算属性的 setter

计算属性默认只有 getter,不过在需要时你也可以提供一个 setter:

computed: {
  fullName: {
    // getter
    get: function () {
      return this.firstName + ' ' + this.lastName
    },
    // setter
    set: function (newValue) {
      var names = newValue.split(' ')
      this.firstName = names[0]
      this.lastName = names[names.length - 1]
    }
  }
}

现在再运行 vm.fullName = 'John Doe' 时,setter 会被调用,vm.firstNamevm.lastName 也会相应地被更新。

原理

在 Vue 2 中,computed 是一种计算属性,用于根据响应式数据的变化自动计算出一个新的值。底层原理涉及到 Vue 的依赖追踪和响应式系统。

底层原理的核心是利用了 JavaScript 的 Object.defineProperty() 方法来实现属性的 getter 和 setter,从而实现对响应式数据的依赖追踪。

当你在组件中定义一个 computed 计算属性时,Vue 会把它转化为一个属性描述符对象,该对象具有 getset 方法。当访问计算属性时,get 方法会被调用,Vue 会去追踪所依赖的响应式数据,并在 getter 函数内部建立一个依赖关系。

一旦计算属性内部依赖的响应式数据发生变化,Vue 就会触发依赖关系,再次调用计算属性的 getter 方法来重新计算相关的值。这样,无论依赖的响应式数据被修改了多少次,计算属性的值都会被缓存起来,不会多次重复计算。

Vue 通过一个名为 Dep(Dependency)的类来管理计算属性和依赖关系。computed 属性的 getter 函数和相关的响应式数据之间的依赖关系会被收集到对应的 Dep 中,当相关响应式数据发生变化时,Vue 会通知 Dep,Dep 会负责通知计算属性进行重新计算。

此外,Vue 还会为每个计算属性创建一个 Watcher(观察者),用于跟踪计算属性的依赖关系和变化。当计算属性所依赖的响应式数据变化时,Watcher 会被触发,并重新计算计算属性的值。

最后,当计算属性中的响应式数据不再被使用时,Vue 会将其与计算属性的依赖关系解绑,避免不必要的计算和性能开销。

总结起来,Vue 2 的 computed 底层原理主要通过 Object.defineProperty()、Dep 和 Watcher 进行依赖追踪和重新计算,实现了计算属性的自动计算和缓存功能,从而提供了更高效和便利的数据处理方式。

vue3的computed的底层原理

在 Vue 3 中,computed 的底层原理相比 Vue 2 有所不同,主要是基于新的响应式系统的设计。Vue 3 使用了基于 ES6 Proxy 的响应式实现,这也影响了 computed 的底层实现方式。

在 Vue 3 中,computed 的底层原理主要包括以下几个关键点:

  1. 使用 ES6 Proxy:Vue 3 中的响应式系统采用了 ES6 Proxy 对象来代理数据,监听数据的访问和修改操作。当访问或修改响应式数据时,Proxy 对象会捕获这些操作,并触发相应的响应式更新。

  2. 基于 Proxy 的依赖追踪:当定义 computed 计算属性时,Vue 3 会使用 Proxy 对象进行依赖追踪。在计算属性的 getter 函数内部访问其他响应式数据时,Proxy 会将这些依赖关系收集起来,建立依赖图谱。这意味着计算属性会自动追踪其所依赖的响应式数据,当这些数据发生变化时,计算属性会自动重新计算。

  3. 缓存计算结果:与 Vue 2 类似,Vue 3 中的 computed 仍然会缓存计算结果,只有当依赖的响应式数据发生变化时才会重新计算计算属性的值。这样可以避免不必要的重复计算,提高性能和效率。

  4. 同步和异步更新:与 Vue 2 不同,Vue 3 的响应式系统支持同步和异步更新。因此,计算属性的重新计算可能会被延迟到下一个微任务(microtask)中执行,以提高整体性能。

总的来说,Vue 3 中 computed 的底层原理基于 ES6 Proxy 的响应式实现,通过代理对象捕获属性访问并建立依赖关系,实现了对计算属性的自动依赖追踪和缓存计算结果。这种基于 Proxy 的响应式系统使得 computed 和其他响应式特性在性能和功能上得到了进一步的优化和提升。

vue2 的computed的更新,只有同步没有异步

在 Vue 2 中,响应式系统的更新是基于 Object.defineProperty 实现的,并不直接支持异步更新。当数据发生变化时,会立即触发相应的更新操作,这意味着数据变化会立即反映在视图上。

Vue 2 的响应式系统是基于侦测变化的方式实现的,使用了 Object.defineProperty 来拦截属性的访问和修改操作,从而在数据变化时立即通知相关的 Watcher 执行更新操作。由于这种实现方式的限制,Vue 2 不容易支持异步更新,更新操作需要立即执行以确保响应性能和数据的一致性。

另外,Vue 2 中的计算属性使用的是普通的依赖追踪方式,当计算属性依赖的响应式数据变化时,会立即重新计算计算属性的值。

而在 Vue 3 中,基于 ES6 Proxy 的响应式系统支持异步更新,数据变更可以被延迟到下一个微任务中进行处理,这意味着一系列的数据变更可以被合并为一个更新操作,以提高性能并避免不必要的更新。Vue 3 的响应式系统利用了 Proxy 对象的特性,可以轻松地实现异步更新,这也是 Vue 3 相比 Vue 2 的一个优化点之一。

因此,Vue 2 之所以不使用异步更新,主要是基于其响应式系统的实现方式以及性能和设计考虑。而 Vue 3 则利用了新的技术和机制,支持了更灵活的异步更新方式,以进一步提升性能和用户体验。

function initComputed (vm: Component, computed: Object) {
	defineComputed(vm, key, userDef)
}
export function defineComputed (
  target: any,
  key: string,
  userDef: Object | Function
) {
  const shouldCache = {}
  sharedPropertyDefinition.get = createComputedGetter(key)
  sharedPropertyDefinition.set = noop
  Object.defineProperty(target, key, sharedPropertyDefinition)
}
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
    }
  }
}

在watcher内有,可以看到,是直接计算的
  evaluate () {
    this.value = this.get()
    this.dirty = false
  }

vue3的computed

1、执行下面代码

packages\vue\examples\composition\test.html

<script src="../../dist/vue.global.js"></script>
<div id="demo">
  <h1>
    <header>{{count}}</header>
    <header>{{count2}}</header>
  </h1>
</div>
<script>
  const { createApp, ref, toRefs, reactive, watch, onMounted, computed } = Vue
  var app = createApp({
    setup() {
      var count = ref(1)
      var count2 = computed(() => {
        debugger
        return count.value * 2
      })
      setTimeout(() => {
        count.value++
      }, 1000);
      return { count, count2 }
    }
  })
  app.mount('#demo')
</script>

2、流程分析

  1. 执行setupCompoent时,会执行computed函数
  2. 获取gettersetter
  3. 执行new ComputedRefImpl(getter, setter),内部代码分成下面两个部分:
    1. this.effect = new ReactiveEffect( getter , fn )
      1. count.value++时,则执行effect.schedule,于是执行fn
      2. fn函数先判断this._dirty === false
      3. 如果是,则this._dirty = true,且执行triggerRefValue,否则啥也不干
      4. 执行triggerRefValue时,内部会执行effect.schedule(),于是执行() => queueJob( instance.update )
    2. get value(){ }
      1. componentUpdateFn执行patch时访问到了该计算属性的值,才会触发get value(){ }
      2. 如果触发了get value(){} ,则执行下面步骤:
      3. 执行trackRefValue(),收集上面声明的this.effect 和 响应式数据关联,当count.value++时,触发上面的fn
      4. if(self._dirty === true)
      5. 是则,self._dirty = true , 且self._value = self.effect.run()
      6. 否则,说明self._dirtyfalse,直接返回self._value

processOn

3、代码分析

packages\reactivity\src\computed.ts

function computed(
  getterOrOptions,
  debugOptions
) {
  let getter
  let setter
  const onlyGetter = isFunction(getterOrOptions)
  if (onlyGetter) {
    getter = getterOrOptions
    setter = NOOP
  } else {
    getter = getterOrOptions.get
    setter = getterOrOptions.set
  }
  const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter,isSSR)
  return cRef
}
class ComputedRefImpl {
  public _dirty = true
  constructor(getter , private readonly _setter) {
    this.effect = new ReactiveEffect(getter, () => {
      if (!this._dirty) {
        this._dirty = true
        triggerRefValue(this)
      }
    }) 
  }
  get value() {
    const self = toRaw(this)
    trackRefValue(self)
    if (self._dirty || !self._cacheable) {
      self._dirty = false
      self._value = self.effect.run()!
    }
    return self._value
  }
  set value(newValue) {
    this._setter(newValue)
  }
}
GitHub 加速计划 / vu / vue
207.54 K
33.66 K
下载
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
最近提交(Master分支:2 个月前 )
73486cb5 * chore: fix link broken Signed-off-by: snoppy <michaleli@foxmail.com> * Update packages/template-compiler/README.md [skip ci] --------- Signed-off-by: snoppy <michaleli@foxmail.com> Co-authored-by: Eduardo San Martin Morote <posva@users.noreply.github.com> 4 个月前
e428d891 Updated Browser Compatibility reference. The previous currently returns HTTP 404. 5 个月前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐