Deep clone in AngularJS

Posted
Comments None

TL;DR

angular.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.

How I bumped into it

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.

Other options?

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;
  • JSON — vanilla js, works surprisingly fast;
  • utilizing structured clone via 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.

Quality

method cycles non-trivial types preserves prototypes functions
angular.copy
_.cloneDeep 1
JSON 🛑
postMessage 🛑
  • non-trivial types is for Date and other built-in objects
  • preserves prototypes is for user-defined custom objects
  • 🛑 means that function fails with an exception instead of silently changing or throwing data away

Quantity

Here are the charts. Average time in milliseconds by the number of nodes in an object.

  • angular.copy
  • _.cloneDeep
  • JSON
  • postMessage
  • custom solution (link)

Chrome results chart

FireFox results chart

Internet Explorer results chart

Reference

Test env

  • CPU: i5-4210M @2.6GHz
  • OS: Win10
  • libraries: Angular 1.6.7, lodash 4.17.4
  • Browsers: Chrome 63, Firefox 56, IE 11, Edge 14

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.

Excel spreadsheet with data

Footnotes

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 =.

Author

Comments

There are currently no comments on this article.

Comment

Enter your comment below. Fields marked * are required. You must preview your comment first before finally posting.





← Older Newer →