Vue: very poorly performance on computed property of computed property that accesses a nested array

Created on 21 Sep 2017  ·  4Comments  ·  Source: vuejs/vue

Version

2.4.4

Reproduction link

https://jsfiddle.net/kz16r91L/

Steps to reproduce

First computed property accesses a nested array and second computed property accesses the first computed property.

What is expected?

The computed property is cached and the repeated calls returns the result immediately.

What is actually happening?

The compute function is not called but each access to the computed property is very slow.


Looks like the computed property, that accesses a nested array, is not cached properly.

Most helpful comment

@SeregPie Your test is wrong because the watcher is called before it: https://jsfiddle.net/ns1n6kwq/1/ I also added a few tests:

  • The second one access the cached values and it is very performant as expected.
  • The third one adds a new object into the base object property. As you can see, the value of testBB is not the correct final value in your test, because the watcher hasn't fired yet. In reality, you are still accessing the cached value (and as such you get the same super fast access to the value).

vue-issue-6660

So the results of your test are incorrect and make your think the watcher is called instantly. Also, you are not using deep: true which make your watcher not detecting the potential changes in the nested arrays (unlike the computed property which does by default), so the comparison is wrong too.


The performance issue comes from Vue building the dependencies when evaluating the computed properties: accessing the computed properties multiple times adds even more dependencies and slows down the component.

One thing I did when having this kind of performance heavy computed properties was to just access the computed property once and assign it into a local variable:

myComputed () {
  const a = this.myOtherComputed
  // use a instead of this.myOtherComputed
}

Example for my proposed solution: https://jsfiddle.net/kLor5ep7/1/

You may find that this is in fact as performant as using a watcher as you can see in this corrected version of your watcher test: https://jsfiddle.net/LrLqmmjr/1/ (The watcher is manually added after the first evaluation of the pTestB computed property so the times are right. It is not added again in the next tests. Also, there is a little overhead compared to the previous without-watcher code because the watcher timer includes the call to test and the Promise constructor.)
Notice the time taken by the watcher to fire and update testB after the new element is added to the array.

tl;dr

Inside your computed properties, use local variables to store the other computed properties and avoid accessing them directly multiple times.

All 4 comments

This is related and explained in https://github.com/vuejs/vue/issues/6284

For now, you can avoid the iteration cost by freezing the Array, if you don't expect it to be mutated. This will no longer be necessary in 3.0 though.

Wait. This is not the cause. The cause is, that the access to an already computed property is very slow. If you put a watcher between two computed properties, then all is working great.
See testBB is now even faster than testAA.
I also did the same in my project and all the changes are still reflected properly, but it is a million times faster.

@SeregPie Your test is wrong because the watcher is called before it: https://jsfiddle.net/ns1n6kwq/1/ I also added a few tests:

  • The second one access the cached values and it is very performant as expected.
  • The third one adds a new object into the base object property. As you can see, the value of testBB is not the correct final value in your test, because the watcher hasn't fired yet. In reality, you are still accessing the cached value (and as such you get the same super fast access to the value).

vue-issue-6660

So the results of your test are incorrect and make your think the watcher is called instantly. Also, you are not using deep: true which make your watcher not detecting the potential changes in the nested arrays (unlike the computed property which does by default), so the comparison is wrong too.


The performance issue comes from Vue building the dependencies when evaluating the computed properties: accessing the computed properties multiple times adds even more dependencies and slows down the component.

One thing I did when having this kind of performance heavy computed properties was to just access the computed property once and assign it into a local variable:

myComputed () {
  const a = this.myOtherComputed
  // use a instead of this.myOtherComputed
}

Example for my proposed solution: https://jsfiddle.net/kLor5ep7/1/

You may find that this is in fact as performant as using a watcher as you can see in this corrected version of your watcher test: https://jsfiddle.net/LrLqmmjr/1/ (The watcher is manually added after the first evaluation of the pTestB computed property so the times are right. It is not added again in the next tests. Also, there is a little overhead compared to the previous without-watcher code because the watcher timer includes the call to test and the Promise constructor.)
Notice the time taken by the watcher to fire and update testB after the new element is added to the array.

tl;dr

Inside your computed properties, use local variables to store the other computed properties and avoid accessing them directly multiple times.

Did someone come up with a solution for the use case, when the problem of a slowdown is accessing such computed property in multiple components (instead of having multiple accesses to a computed in another computed) 😅?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

paceband picture paceband  ·  3Comments

seemsindie picture seemsindie  ·  3Comments

guan6 picture guan6  ·  3Comments

bdedardel picture bdedardel  ·  3Comments

fergaldoyle picture fergaldoyle  ·  3Comments