Blog is available in backup mode, go to github-pages for newer posts
Back to articles listangular.copy
from AngularJS 1.x is quadratic-slow on large objects, especially in IE.
Use JSON.stringify
/JSON.parse
for simple objects or _.deepClone
for objects with Date
objects or cyclic references. Actually, if you don't clone objects of more than 1000 nodes, this shouldn't bother you.
The other day I faced with a need to deeply clone an object from the redux store and since we're using angular I initially thought about angular.copy, but then I remembered, that its use is being discouraged because of "being very slow in IE".
Maybe, it was an old bug an now it's fixed? A quick search showed that there's an issue on github and it's still open.
OK, but how exactly slow is that function? People report it being slow on large objects.
I quickly created a generator of random objects of certain nesting depth and total node count, and a suite to test function. Here's a gist with everything combined. In order to measure one needs to run suite
with deep cloning function passed as an argument.
So angular.copy
turned out to be really slow. Even though it was OK for the case I started with, I went too far to stop researching the question. What other options do we have? Of course, I can write a stupid recursive cloner in ~10 lines of code and call it a day, but since I'm an ENTERPRISE developer and that day I didn't feel like writing my own solution, I considered existing libraries and really trivial methods, which could be written in a single line.
Actually, somewhere near the end of writing that post, I implemented simple cloner, and ... discovered nothing new. It's faster than much more elaborate _.deepClone
, but not that much. You can see a green line on the chart for the Chrome below.
So I stuck with the following four methods:
angular.copy
— which I started from;lodash.cloneDeep
— a method from the most popular utility library. I think similar methods from other libs would work roughly the same;postMessage
— also vanilla js w/o serialization overhead.The last option is the most interesting one. The thing is browsers have built-in support for a magic called structured cloning of objects, but it's not directly accessible via JavaScript. It is hidden inside postMessage API, so we just need to send a message to the same window. The only problem I expected was that such a function would be asynchronous. Alas, API call blows up with an error on function properties. And what's worse, it seems to leak memory in IE: the full suite failed with "out of memory" so I had to run it in 3 parts. During tests, I just used postMessage
w/o getting it back. That was enough to measure cloning process. More or less proper implementation is available in the same gist.
method | cycles | non-trivial types | preserves prototypes | functions |
---|---|---|---|---|
angular.copy | ✅ | ✅ | ✅ | ✅ |
_.cloneDeep | ✅ | ✅ | ✅ | ✅1 |
JSON | 🛑 | ❌ | ❌ | ❌ |
postMessage | ✅ | ✅ | ❌ | 🛑 |
Date
and other built-in objectsHere are the charts. Average time in milliseconds by the number of nodes in an object.
Test env
I also checked angular.copy
in Safari on @sublimeye's Mac, but results are uninterestingly similar to Chrome apart from his CPU (i7-4770HQ @2.2GHz) is significantly faster than mine. Edge is almost the same as IE.
1 — documentation for clone
says that functions are transformed into empty objects and cloneDeep
works the same. But that's true only if we pass a function directly to the cloneDeep
. Any nested function property is just copied by a normal =
.