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

Do not slice `classNames` and `classNameBindings` speculatively. #14389

Merged
merged 3 commits into from Oct 27, 2016

Conversation

Projects
None yet
2 participants
@rwjblue
Member

rwjblue commented Sep 30, 2016

The vast majority of the time these arrays are completely static on the prototype, there are relatively few instances where we actually need to slice them but we were speculatively doing this "just in case" you wanted it.

This change does not remove the ability to have custom classNames / classNameBindings per instance, but it does make it so that folks wanting to do this would need to do this.classNames.slice() first.

This avoids many extraneous array allocations, and one more layer of wasted work during initial render.

Robert Jackson
Do not slice `classNames` and `classNameBindings` per instance.
The vast majority of the time these arrays are completely static on the
prototype, there are relatively few instances where we actually need to
slice them but we were speculatively doing this "just in case" you
wanted it.

This change does not remove the ability to have custom `classNames` /
`classNameBindings` per instance, but it does make it so that folks
wanting to do this would need to do `this.classNames.slice()` first.

This avoids many extraneous array allocations, and just one more layer
of wasted work during initial render.
@chancancode

This comment has been minimized.

Show comment
Hide comment
@chancancode

chancancode Sep 30, 2016

Member

Do we think we can get away with this? I suspect it would probably be considered a breaking change since the slice was added a while ago in response to this use case. If we think it's okay to make this change I would be happy to see this go though.

However, if we can't, I have an alternative that I am working on. My plan is that we make these a getter and try to detect access them during construction (with some POST_INIT trick), and if they are accessed, we assume they are being mutated and returned a sliced version (only during init).

I plan to apply this to a few things such as per-instance tagName, etc to emit optimized component templates. If we detect that any of these features are used, we can set a flag on the class indicating the component is unoptimizable and fallback to the generic template we have today.

If that works out, we might be able to get the same performance improvements without the breaking change.

Member

chancancode commented Sep 30, 2016

Do we think we can get away with this? I suspect it would probably be considered a breaking change since the slice was added a while ago in response to this use case. If we think it's okay to make this change I would be happy to see this go though.

However, if we can't, I have an alternative that I am working on. My plan is that we make these a getter and try to detect access them during construction (with some POST_INIT trick), and if they are accessed, we assume they are being mutated and returned a sliced version (only during init).

I plan to apply this to a few things such as per-instance tagName, etc to emit optimized component templates. If we detect that any of these features are used, we can set a flag on the class indicating the component is unoptimizable and fallback to the generic template we have today.

If that works out, we might be able to get the same performance improvements without the breaking change.

@rwjblue

This comment has been minimized.

Show comment
Hide comment
@rwjblue

rwjblue Sep 30, 2016

Member

@chancancode - Thank you for reviewing! Yes, I agree that this is a tad bit tricky, but I still think this is something that we can do. No capabilities are removed by this PR (the only use cases I have seen of this are already .slice() ing), this was not documented functionality (mutating during init) and the pattern used is generally something you should avoid in JS (mutating arrays on the prototype from within an instance).

We have done this sort of thing before, so there is precedent. For example, we made helpers params / hash frozen objects during the 2.9 cycle for similar optimizations.

Member

rwjblue commented Sep 30, 2016

@chancancode - Thank you for reviewing! Yes, I agree that this is a tad bit tricky, but I still think this is something that we can do. No capabilities are removed by this PR (the only use cases I have seen of this are already .slice() ing), this was not documented functionality (mutating during init) and the pattern used is generally something you should avoid in JS (mutating arrays on the prototype from within an instance).

We have done this sort of thing before, so there is precedent. For example, we made helpers params / hash frozen objects during the 2.9 cycle for similar optimizations.

@rwjblue

This comment has been minimized.

Show comment
Hide comment
@rwjblue

rwjblue Sep 30, 2016

Member

Updated to ensure that concatenatedProperties are frozen arrays in debug mode.

Member

rwjblue commented Sep 30, 2016

Updated to ensure that concatenatedProperties are frozen arrays in debug mode.

Robert Jackson
Ensure concatenated properties are frozen.
Mutating objects on the prototype (which includes concatenated
properties) is not good. This changes the functionality of concatenated
properties to freeze the resulting array while in debug mode, to make it
clear that mutating them without slicing first is not good.

@rwjblue rwjblue merged commit cb9959b into emberjs:master Oct 27, 2016

1 check passed

continuous-integration/travis-ci/pr The Travis CI build passed
Details

@rwjblue rwjblue deleted the rwjblue:avoid-slicing-classNames branch Oct 27, 2016

bgentry added a commit to bgentry/ember-test-selectors that referenced this pull request Jan 24, 2017

attributeBindings is frozen, slice it before push
As noted in the Ember v2.11 release notes, concatenated properties such
as attributeBindings are frozen in debug builds. This means we cannot
push directly onto the attributeBindings array without first copying it
via slice.

Without this change, users may see errors like this when the
`attributeBindings.push()` call is made:

Uncaught TypeError: Can't add property 2, object is not extensible

References:

emberjs/ember.js#14389
emberjs/ember.js#14601

bgentry added a commit to bgentry/ember-test-selectors that referenced this pull request Jan 24, 2017

attributeBindings is frozen, slice it before push
As noted in the Ember v2.11 release notes, concatenated properties such
as attributeBindings are frozen in debug builds. This means we cannot
push directly onto the attributeBindings array without first copying it
via slice.

Without this change, users may see errors like this when the
`attributeBindings.push()` call is made:

Uncaught TypeError: Can't add property 2, object is not extensible

References:

emberjs/ember.js#14389
emberjs/ember.js#14601

bgentry added a commit to bgentry/ember-test-selectors that referenced this pull request Jan 24, 2017

attributeBindings is frozen, slice it before push
As noted in the Ember v2.11 release notes, concatenated properties such
as attributeBindings are frozen in debug builds. This means we cannot
push directly onto the attributeBindings array without first copying it
via slice.

Without this change, users may see errors like this when the
`attributeBindings.push()` call is made:

Uncaught TypeError: Can't add property 2, object is not extensible

References:

emberjs/ember.js#14389
emberjs/ember.js#14601

Turbo87 added a commit to simplabs/ember-test-selectors that referenced this pull request Jan 25, 2017

attributeBindings is frozen, slice it before push (#59)
As noted in the Ember v2.11 release notes, concatenated properties such
as attributeBindings are frozen in debug builds. This means we cannot
push directly onto the attributeBindings array without first copying it
via slice.

Without this change, users may see errors like this when the
`attributeBindings.push()` call is made:

Uncaught TypeError: Can't add property 2, object is not extensible

References:

emberjs/ember.js#14389
emberjs/ember.js#14601
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment