-
-
Notifications
You must be signed in to change notification settings - Fork 33.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve Performance for Arrays that are not extensible (and therefore not reactive) #6284
Comments
You seem to misunderstand what
(from MDN, emphasis mine) Vue doesn't add any new properties - it adds getters and setters for existing properties, which is fine with Object.preventExtensions. Demo in vanilla Javascript: https://jsfiddle.net/Linusborg/ze47p6ta/ You probably want to rather use |
@LinusBorg perhaps this post should be titled differently, but please see the console timings in the reproducer I posted to see that there is clearly a bug in Vue, as it is recursively walking my array in an effort to make its elements reactive (which I do not want). The following code should not take 17ms: console.time('setting big array');
vm.arr = Object.preventExtensions(bigArray);
console.timeEnd('setting big array'); Vue does a similar thing with plain objects, however I can prevent this behavior with This is causing significant performance degradation in my project as I have a very large array that I need to be able to index into, but don't need to be reactive. |
Here is a simpler reproducer that doesn't involve https://jsfiddle.net/smh4fnms/ Edit: |
EDIT: The following is wrong, see my next reply.
|
I have to revert myself here, and apologize for claiming to be sure how it worked in this instance when in reality I was wrong, as it turns out. In fact, we were both wrong: Vue does respect Object.preventExtensions. You can test this out with your original jsfiddle. just log the array to the console after it was added to Vue, once with, once without You will notice that the one without the I also checked this by adding breakpoints in vue.js on line 937 - when So apologies again for claiming something factually wrong, but this issue stays closed because Vue is already behaving like you want it to. |
In Chrome Would it be possible to introduce a different way of indicating to Vue not to iterate this array? I do not actually need this object to be frozen, I am only doing so to change the behavior of Vue. |
Side note: The |
That has been discussed a couple of times previously already, and won't happen. If you really want to skip all reactivity, just add the array directly to the instance instead of assigning it to a property derived from created() {
this.bigArray = bigArray
} |
Okay, I'm going to take a step back because I think some of my initial debugging led me to think the problem was something that it wasn't. This reproducer is very close to my actual scenario. I am building a virtual grid, where I have a large source array, and I compute some start index (normally this would be based on scroll position, in this case I've just added a button to simulate scrolling), and the template renders these rows. In this jsfiddle, I have My issue at its core has nothing to do with reactivity, and is solely about performance. This does not seem like something that should perform this poorly. I realize this issue has kind of gone off the rails from what I originally posted, so let me know if I should open a new issue. |
I'm not sure a new issue is in order - the behaviour with As I said, if you need raw performance and no reactivity, add the array as a normal instance propety: https://jsfiddle.net/Linusborg/1s3hzsd5/ Further discussions about improving your specific performance challenges should probably rather happen on forum.vuejs.org, unless you have a specific improvement to suggest - that would warrant a new issue as a feature request. |
@LinusBorg with due respect, I find it really hard to believe that this is considered acceptable performance, especially considering the fact that I've marked the array as not extensible. The entire array (and sub-arrays) are being iterated every time the In this reproducer, the template renders 10 rows, and each of these reads I believe this can be fixed by changing this line to: if (Array.isArray(value) && Object.isExtensible(value)) { I haven't been able to build/test due to #4338 however, so I'm not sure if this will break any tests, but I do believe this makes sense given the fact that Vue does respect |
Good idea, but honestly, I'm not sure about the consequences. @fenduru I reopened and rename the topic to better reflect the issue. Could you edit our OP with a comment linking down to where we got to the heart of the issue? thanks. @yyx990803 You will have to chime in here ... Evan, @fenduru is suggesting to keep I was thinking that this could interfere with reactive objects within the not-extensible array. Accessing a prop of such an object would trigger the object's own reactive getter, but only accessing the object as a whole (and e.g. passing it to a child component, or reading the keys of the object into an array with Object.keys() ...) might go unnoticed when skipping I'm not deep enough in this topic to say... If we have to walk the array with Spontaneous idea, and I could be totally of the rails here:
// a Set would be much better for this...
if (Array.isArray(value) && (Dep.target._arrayCache.indexOf(value) === -1)) {
dependArray(value)
Dep.target._arrayCache.push(value)
}
// dep.js
export function popTarget () {
Dep.target._arrayCache = null
Dep.target = targetStack.pop(
} Now the code has to walk the _arrayCache each time to check for presence of |
According to your description of needing properties to be reactive at the top level, I think the real issue here is whether the data object or the array should be observed shallowly instead of whether |
This behavior would be consistent with the behavior for Objects that have been
I was thinking about this as well - there is a performance hit for all applications right now, even ones that do want the reactivity because @jacelynfish To clarify what I mean by "reactive at the top level", I mean I only want the reactivity system to detect when the array reference itself has changed. My array is immutable, the only time any of its contents will change is when the array reference itself changes, so Vue being reactive on the arrays contents is a waste of performance for me. |
@fenduru but an array won't be immutable only by calling |
@jacelynfish in my application I have guarantees that the array is immutable regardless of whether the object can technically be mutated. |
@LinusBorg @yyx990803 any thoughts on this? I would like to file a pull request for this however I am unable to get a working dev environment per #4338 |
@jacelynfish @LinusBorg , @fenduru 's idea of using Object.preventExtensions will disable the array's reactiveness (no ob is added), therefore , the changes that happen to the elements will not be watched, so, the array being immutable or not, is not the issue here, right? |
@fenduru sorry for the late response. Yes, the way to get rid of this cost is by preventing Ultimately, Luckily, with the planned rewrite of the reactivity system using ES6 Proxy, we no longer need this work around and should see great perf boost in the scenarios you've been running into. |
@yyx990803 I can't replicate the behavior you are expecting in that case: https://jsfiddle.net/3x1y6yg9/ I understand that I have opened #6467 and think it should be considered unless the scenario above can be shown to differ between objects and arrays (even then it seems kind of unreasonable for someone to explicitly opt out of change detection on an array and then be surprised when change detection doesn't happen within that array). Edit: As a side note, while I think that ES6 Proxy is perfect for this kind of use case, it is not supported by IE11 and can't be polyfilled. IE is the most impacted browser for all things performance and is not going away any time soon, so I don't think we can ignore this issue in favor of ES6 proxies |
@fenduru this is because you have one extra level of nesting, so the See this case: https://jsfiddle.net/yyx990803/y8o98yjv/1/ |
@yyx990803 I see, however this is actually an inconsistency between Objects and Arrays which is exactly my point. If you change that array to be an object that is frozen, the |
@fenduru that's true. I guess for now it's actually better to keep them consistent. |
Version
2.4.2
Reproduction link
https://jsfiddle.net/hgfx55h6/
Steps to reproduce
Object.preventExtensions
What is expected?
Vue's reactivity system should not do anything with this array, and setting this property should have the same runtime as setting a string on the viewmodel (i.e. O(1) )
What is actually happening?
The
dependArray
function is invoked recursively on the object every time the array is accessed, causing the setting of that property to take linear time. This can occur many times per tick.This reproducer is very close to my actual scenario. I am building a virtual grid, where I have a large source array, and I compute some start index (normally this would be based on scroll position, in this case I've just added a button to simulate scrolling), and the template renders these rows.
In this jsfiddle, I have
Object.preventExtensions()
'd the array and never change it. The only thing I am changing is the start index, however doing so has an enormous amount of lag. When I profile this I see:My issue at its core has nothing to do with reactivity, and is solely about performance. This does not seem like something that should perform this poorly.
The text was updated successfully, but these errors were encountered: