-
Notifications
You must be signed in to change notification settings - Fork 27.5k
fix(copy): preserve prototype chain when copying object #5063
fix(copy): preserve prototype chain when copying object #5063
Conversation
Can you provide an explicit example of the use case that motivates this change? |
I have instances with "typed" properties I need to persist with JSON format. For instance, I could have the following object whose one of its properties is an instance of
This record is persisted in JSON with its type inlined because the property value is not JSON native type:
I choose to override default
Unfortunately, Angular resource make a copy of that deserialized object and the final object retrieved is:
Some code on my side needs properties to respond correctly to
Am I wrong in my reasoning? |
I'm sorry, but I wasn't able to verify your CLA signature. CLA signature is required for any code contributions to AngularJS. Please sign our CLA and ensure that the CLA signature email address and the email address in this PR's commits match. If you signed the CLA as a corporation, please let me know the company's name. Thanks a bunch! PS: If you signed the CLA in the past then most likely the email addresses don't match. Please sign the CLA again or update the email address in the commit of this PR. |
I have just fixed the e-mail of all PR commits and I have signed the CLA with the right e-mail. |
I have rebased the commits onto master and refactored them in two:
@petebacondarwin, I have also unpublished changes (based on this PR) to copy getters and setters that could fix #5085 but I not sure if they should be integrated here. |
As Angular 1.3 discontinues IE8 and as I do not expect this PR to land on Angular 1.2x branch, I will remove failing IE8 tests in the very near future. |
I have rebased the PR onto master and I have dropped IE8 support. All tests pass now. |
@@ -770,7 +770,7 @@ function copy(source, destination){ | |||
} else if (isRegExp(source)) { | |||
destination = new RegExp(source.source); | |||
} else if (isObject(source)) { | |||
destination = copy(source, {}); | |||
destination = copy(source, Object.create(Object.getPrototypeOf(source))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line means that backporting this to 1.2.x is impossible or difficult.
I think we could get around this, though, using a strategy similar to what we have in the injector:
function Constructor() {}
Constructor.prototype = isFunction(source.constructor)
? source.constructor.prototype
: Object.prototype;
destination = copy(source, new Constructor());
I'm not positive that would work, but I think it would. It might be a bit expensive, though.
Thank you very much @caitp for your comment. In the first version of this PR, I tried to support IE<=8 by creating the following shim function to support Object.create into Angular.js: /**
* Creates object through Object.create or polyfill for IE <= 8
*
* @private
* @param {Object} proto - The object representing the prototype of the new object to create
* @returns {Object} Newly created object with proto as prototype
* */
var createObject = (function () {
function Constructor() {}
if(typeof Object.create === "function") {
return Object.create;
}
else { // msie <= 8
return function createObject(proto) {
Constructor.prototype = proto;
return new Constructor();
};
}
}()); As you can see,
This way, it should not be too expensive. If needed, I could make a separate PR for this or try to re-include it here. |
I have realized that there's also a problem for As a side note, I didn't expect this PR to land to 1.2.x branch. I found these shims too overkill just to support IE8 with 1.2.x. But if needed I could try to fix them. |
You can get the prototype of the constructor via the cobstructor property, if one exists. John Resig shows a polyfill at http://ejohn.org/blog/objectgetprototypeof/, which despite the caveats, should generally work "well enough' |
Thanks for the fix @gentooboontoo. This resolved an issue I was having using moment.js with angular. Hopefully the PR gets merged |
Hi, I am having the same issues with moment.js. I would highly appreciate integrating it. Can 100% confirm @shaneneuerburg It fixed my issue as well |
I'm also having trouble with that same momentjs issue. This would be pretty handy. From what I gather this isn't compatible with the latest 1.2.x branch? Would anyone know the way to adapt it? |
@caitp @petebacondarwin I have rebased the PR onto master branch (the PR was outdated by some changes). I think this PR targets v1.3.x branch although it works with 1.2.x branch and non IE8 browsers. If needed, I could re-add IE8 support I removed earlier (and fixed according to previous @caitp's comment) but it would be a little bit ugly as v1.3 is approaching. |
I applied this patch and it solved a very annoying problem with $watch on It would be great to see this merged. On Tue, Jun 10, 2014 at 7:25 PM, Julien Sanchez notifications@github.com
|
+1 (given #7846) |
Are there plans to include this in |
@lgalfaso - do you have a view on this? It seems like a reasonable change. |
So far, angular.copy was copying all properties including those from prototype chain and was losing the whole prototype chain (except for Date, Regexp, and Array). Deep copy should exclude properties from the prototype chain because it is useless to do so. When modified, properties from prototype chain are overwritten on the object itself and will be deeply copied then. Moreover, preserving prototype chain allows instanceof operator to be consistent between the source object and the copy. Before this change, var Foo = function() {}; var foo = new Foo(); var fooCopy = angular.copy(foo); foo instanceof Foo; // => true fooCopy instanceof Foo; // => false Now, foo instanceof Foo; // => true fooCopy instanceof Foo; // => true The new behaviour is useful when using $http transformResponse. When receiving JSON data, we could transform it and instantiate real object "types" from it. The transformed response is always copied by Angular. The old behaviour was losing the whole prototype chain and broke all "types" from third-party libraries depending on instanceof.
Just rebased onto master. |
Looks reasonable to me. We should make a note about the breaking change (the new behavior is different) and incompatibility with ie8. |
If we want to backport the fix into 1.2.x, this form of Object.create can be polyfilled, and |
We are changing the behavior. So we should not backport this. On Fri, Jun 27, 2014, 6:42 AM, Caitlin Potter notifications@github.com
|
Think of the statement as a note for the next person to come along saying "I want the new features of 1.3, but I need to support IE8, what can I do" =) |
Maintain a patch. It's not ideal but you are supporting obsolete browser
|
I have initially made a branch with missing IE8 polyfills but it made the change ugly and clumsy (especially in branch 1.3 which does not support IE8). |
@gentooboontoo that's fine, don't worry about it. We aren't testing IE8 so it's not a big deal to not include such fixes here, since we'll never know if they break or not |
@petebacondarwin for 1.3.x, I think this is a no-brainer that we should land this. For 1.2.x I would not back port it |
what stops you collaborators from pulling? |
@leider nothing whatsoever --- I think Pete is merging this one |
Thanks a lot @petebacondarwin and @caitp. |
Thanks from me too. However this has broken something for me - noted here. Thoughts? Not sure whether comments on commits get to the right people... hence the 'repost' |
I have made another PR (#8034) improving this one preserving own property descriptors and including own non-enumerable properties. |
So far, angular.copy was copying all properties including those from prototype chain and was losing the whole prototype chain (except for Date, Regexp, and Array). Deep copy should exclude properties from the prototype chain because it is useless to do so. When modified, properties from prototype chain are overwritten on the object itself and will be deeply copied then. Moreover, preserving prototype chain allows instanceof operator to be consistent between the source object and the copy. Before this change, var Foo = function() {}; var foo = new Foo(); var fooCopy = angular.copy(foo); foo instanceof Foo; // => true fooCopy instanceof Foo; // => false Now, foo instanceof Foo; // => true fooCopy instanceof Foo; // => true The new behaviour is useful when using $http transformResponse. When receiving JSON data, we could transform it and instantiate real object "types" from it. The transformed response is always copied by Angular. The old behaviour was losing the whole prototype chain and broke all "types" from third-party libraries depending on instanceof. Closes angular#5063 Closes angular#3767 Closes angular#4996 BREAKING CHANGE: This changes `angular.copy` so that it applies the prototype of the original object to the copied object. Previously, `angular.copy` would copy properties of the original object's prototype chain directly onto the copied object. This means that if you iterate over only the copied object's `hasOwnProperty` properties, it will no longer contain the properties from the prototype. This is actually much more reasonable behaviour and it is unlikely that applications are actually relying on this. If this behaviour is relied upon, in an app, then one should simply iterate over all the properties on the object (and its inherited properties) and not filter them with `hasOwnProperty`. **Be aware that this change also uses a feature that is not compatible with IE8.** If you need this to work on IE8 then you would need to provide a polyfill for `Object.create` and `Object.getPrototypeOf`.
So far, angular.copy was copying all properties including those from
prototype chain and was losing the whole prototype chain (except for Date,
Regexp, and Array).
Deep copy should exclude properties from the prototype chain because it
is useless to do so. When modified, properties from prototype chain are
overwritten on the object itself and will be deeply copied then.
Moreover, preserving prototype chain allows instanceof operator to be
consistent between the source object and the copy.
Before this change,
Now,
The new behaviour is useful when using $http transformResponse. When
receiving JSON data, we could transform it and instantiate real object
"types" from it. The transformed response is always copied by Angular.
The old behaviour was losing the whole prototype chain and broke all
"types" from third-party libraries depending on instanceof.
This PR is somewhat related to issues #4996, #3767 and #1427.