This repository has been archived by the owner on Apr 12, 2024. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(Angular.copy): preserve prototype chain when copying objects
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 #5063 Closes #3767 Closes #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`.
- Loading branch information
b59b04f
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.
Unfortunately this change to include the
source.hasOwnProperty(key)
check breaks properties that are defined via getters/setters. To demonstrate the problem outside of Angular (via CoffeeScript, although this is clearly entirely legit in Javascript), here is an example, code reproduced below:Per the docs for
hasOwnProperty
:So
a.hasOwnProperty('test') == false
is as expected.But this does break things for people who are defining properties as getters/setters (read: me at least), a way that seems perfectly legitimate. In my case, I am using these getters/setters to effectively create a pub/sub eventing model of changes on objects (liberal use of
$watch
was killing my application) - theconsole.log 'We just set test to ', v
is effectively a basic demo of that. Whilst in theory I could use my own, home-grownangular.copy
, it would be nice to have the core-exposed function work.Is the intention of this call,
source.hasOwnProperty(key)
, to effectively test whether the property is enumerable or not?b59b04f
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.
@myitcv - can you create an issue with a unit test that demonstrates this. I think it is a valid problem that we need to fix.
b59b04f
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.
@myitcv - hold on a minute - the
test
property is being attached to the prototype so it is not a property on thea
object.b59b04f
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.
@myitcv - I am not convinced that this is an issue. If the property is defined on the prototype then it gets passed through to the copy, simply because the copied object uses the same prototype as the source.
See this coffeescript example
b59b04f
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.
@petebacondarwin - agreed, the getters/setters are defined on the prototype. Furthermore, in this case they are defined as
enumerable
hence we would expect to see them when iterating the properties ofa
._test
on the other hand is defined on the objecta
and, more importantly, is defined as non-enumerable. HenceBut because the getters/setters are not direct properties we will not see them via:
The getters/setters are, however, properties nonetheless. So what I am arguing for is that
angular.copy
should be defined to use the(k for k of a)
version because this correctly iterates the enumerable properties ofa
, whether direct properties or getters setters. If people (and this could include Angular core) don't want properties copied, make them non-enumerable, whether getters/setters or direct properties:_test
is an example of the latter.With reference to your linked example, the problem with the copy as implemented is as follows:
Why is this? Well we didn't iterate over
test
because of theown
restriction. Instead I propose the followingworkingMakeCopy
:Link to updated CoffeeScript example
b59b04f
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.
@petebacondarwin - just created #8032 with a test case (and proposed fix). Thoughts?
b59b04f
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.
@myitcv @petebacondarwin I have made a PR (#8034) which preserves own property descriptors (including getters/setters) and also keep own non-enumerable properties when copying object. @myitcv, could it solve your issue?