The Roost

Programming, palaver, puffins!

How to copy arrays and objects in Javascript

, , , , ,

There are a few things that trip people up with regards to Javascript. One is the fact that assigning a boolean or string to a variable makes a copy of that value, while assigning an array or an object to a variable makes a reference to the value. The trouble this causes can range from befuddlement - when two variables you assume are separate are in fact references to the same value - to frustration when you realize there is no native way to tell the Javascript engine to pass a value rather than a reference.

In PHP, for example, all assignments make copies unless you explicitly tell the engine to pass a reference using the =& operator. If only it were so simple in Javascript.

I'm pretty much reinventing the wheel with this post since this issue has been solved before, but the reason I'm writing it is because up until recently I had always been designing custom functions to copy objects of types I designed myself. Very efficient on a per-case basis, but not very reusable. There had to be a way to make such a thing work for any and all arrays and objects. So I searched the interwebs and was enlightened.

Firstly, arrays. Surprisingly, arrays are easy to copy because a couple of native Array object methods actually return a copy of the array. The easiest to use is the slice() method:

var foo = [1, 2, 3];
var bar = foo;
bar[1] = 5;
alert(foo[1]);
// alerts 5

var foo = [1, 2, 3];
var bar = foo.slice(0);
bar[1] = 5;
alert(foo[1]);
// alerts 2

The slice(0) method means, return a slice of the array from element 0 to the end. In other words, the entire array. Voila, a copy of the array. The only caveat to remember here is that this method works if the array contains only simple data types, like numbers, strings and booleans. If the array contains objects or other arrays (a multi-dimensional array), then those contained "objects" will be copied by reference, retaining a connection with the source array. In such a case you will need to copy the array as a full-fledged object.

Objects are trickier because there is no native method which returns a copy of the object. So instead we add one ourselves using a prototype method:

Object.prototype.clone = function() {
  var newObj = (this instanceof Array) ? [] : {};
  for (i in this) {
    if (i == 'clone') continue;
    if (this[i] && typeof this[i] == "object") {
      newObj[i] = this[i].clone();
    } else newObj[i] = this[i]
  } return newObj;
};

Notice the recursion going on; isn't it divine? smile The clone() method steps through the properties of any object one by one. If the property is itself an object or array, it calls the clone() method on that object too. If the property is anything else, it just takes the value of the property. Then the result received is assigned to a property of the same name in a new object.

Finally, after we're done stepping through all properties, return the new object which is a copy of - not a reference to - the old object. A call to the clone method is as simple as this:

var foo = {a: 1, b: 2, c: 3};
var bar = foo;
bar.b = 5;
alert(foo.b);
// alerts 5

var foo = {a: 1, b: 2, c: 3};
var bar = foo.clone();
bar.b = 5;
alert(foo.b);
// alerts 2

Alphanum: Javascript Natural Sorting AlgorithmDrag and Drop on a Shoestring

Comments

Mark 'Tarquin' Wilton-Jonestarquinwj Tuesday, March 4, 2008 10:02:22 AM

Really nice and sweet bit of code smile

But there are some bugs:

1. typeof(null) == 'object' -> throw

if (this[i] && typeof this[i] == "object") {


2. for...in will find the "clone" property on the prototype chain (this is why adding properties to the Object.prototype is often considered bad practice), and will add it as a referenced function as an individual property of the object instance.

for (i in this) {
if( i == 'clone' ) continue;


(Of course, this will break if an individual object instance already has an unrelated property called "clone" but the whole code will break in that case anyway. You could also use hasOwnProperty to check if it is from the prototype and does not need to be copied.)

3. functions are always referenced, not copied. Doubt there is anything you can - or should - do about that.

Brian HuismanGreyWyvern Tuesday, March 4, 2008 3:36:30 PM

Thanks for your patches! I have updated the article. smile

bhengh Thursday, July 10, 2008 5:43:56 AM

Thank you very much for this article! This has bitten me a few times in the past.

Something to note: If you are working with multidimensional arrays (arrays of arrays), the slice() method won't work. It will work for the first level of the array, but any arrays inside will be passed as a reference. Your clone() method, however, works brilliantly on multidimensional arrays. Thanks again.

Brian HuismanGreyWyvern Monday, July 14, 2008 1:46:20 PM

Originally posted by bhengh:

Something to note: If you are working with multidimensional arrays (arrays of arrays), the slice() method won't work.


Yeah, I've actually come across this situation a couple times since I wrote the article, but I haven't had the opportunity to make a note of it in the text. I'll update it now, thanks for reminding me smile

m0ose Tuesday, December 9, 2008 8:42:22 PM

This code works well and for all sorts of things, but it seems to be slow when copying big 2d arrays.
I tried it in a maze solving script and the script took ten times as long. On the bright side it really did simplify the code, which contains a bunch of for-loops to copy arrays.

Erik SodervallEscot Friday, June 5, 2009 1:45:28 PM

This code copies all properties and functions, even inherited. I would suggest that is not what you want.

/* .. your clone function here ..*/
Object.prototype.sillyExample=3;
a=new Object();
alert(a.sillyExample);
b=a.clone();
Object.prototype.sillyExample=5;
if(a.sillyExample!=b.sillyExample)
alert('prototypes differ');

My suggestion to fix this is to replace
if(i=='clone') continue;
with
if(!this.hasOwnProperty(i)) continue;

I'm not a JavaScript guru so I can't tell if this is the best way to handle it. But it seam to work better for me.

/Escot

Albert S.albertschlef Tuesday, September 1, 2009 9:22:57 PM

I tried to use your code but it seems there's some inifinite recursion here. I'm not sure. The CPU is at %100 and the page hangs. Whatever, I googled for "javascript clone" and found some good code. It's a pity this useful utility function isn't built-in.

David Pitmandrpitman Wednesday, October 28, 2009 5:21:40 AM

if you are sure that it is safe to do so, you can always do:

var clone_of_object = eval ( old_object.toSource() );

That worked fine for my purposes. The code in the top post errored out with too much recursion, even with the fix by Escot.

Of course, anything with 'eval' you'll want to ensure that the context is safe, but as this probably is a client side script, you shouldn't have anything too important there anyway.

Enjoy.

Jonnypi3141592653589 Monday, June 28, 2010 1:12:27 AM

When we all get together and redo the Internet from scratch (yes it desperately needs to be done). I want to hire you to design the entire distributed application spec.

Seriously JavaScript is brain-dead dumb, but I am amazed how creative humanity is because we've somehow we've managed to erect all these castles and fortresses of the web with the blunt and feeble stick of JavaScript.

That said, here's another random JavaScript tip for anyone that I agree is a super-duper life-saver that will keep you from getting fired or even torched alive from a spire:

var somenumber = (dom.getAttribute('expectednumber') || undefined);

if (somenumber != undefined)
{
return parseInt (somenumber);
}

As opposed to relying on:

var somenumber = dom.getAttribute('expectednumber');
if (somenumber != undefined)
{
return parseInt(somenumber); // NaN if somenumber == '' !!!
}

Jonnypi3141592653589 Monday, June 28, 2010 1:15:50 AM

Also, we need a method to propagate/merge an object with/into another object, such as Sencha (Ext-JS) Ext.apply and applyIf functions:

Here's my version:

// Merge the object 'from' into the object 'to' overwriting any properties found in 'to'
function $propagate$ (to, from)
{
if (from != undefined)
for (var k in from)
if (from.hasOwnProperty(k))
{
if ($isObj$(from[k]))
{
to[k] = $propagate$ (to[k] || {}, from[k]);
} else
to[k] = from[k];
}

return to;
}

// This function is nasty and ugly, is there a better way? most likely.
function $isObj$ (o)
{
return o != undefined && typeof o == 'object' && !(o instanceof Array || o instanceof Date || o instanceof Number || o instanceof String);
}

You can accomplish an object copy with: $propagate$({}, obj);

Jonnypi3141592653589 Monday, June 28, 2010 1:18:25 AM

I think this is generally a good idea. 99% of the time it is safe to do because 99% of the time the data you are eval() 'ing is yours and entirely under your control (if any XSS issues arise, then that just means you have bigger problems anyhow).

Most importantly, using eval() is a built-in web-browser method, unbelievably more efficient than a custom javascript-written JSON parser. Well unless you use Chrome, which compiles it down to machine code, then there's probably little benefit.

Daniel Zegarradzegarra Monday, November 29, 2010 6:40:47 PM

Great man. I just realized that javascript supports referenced objects.

crackerjacker Tuesday, August 23, 2011 1:51:50 PM

To clone any javascript object (DOM objects doesn't have a toSource function) there is a simpler solution:

eval(myObject.toSource());

Cyphericypher Friday, September 16, 2011 6:57:13 PM

I'm sure everyone is already aware that javascript functions are merely objects. Copying these objects by value is a simple matter of using the new keyword as in the following example:
function Elf(){
this.society="Drow";
this.name="Drizzt";
}
var myOldElf=new Elf();
var myNewElf=new Elf();
myNewElf.name="Brizza";
alert(myOldElf.name); //displays Drizzt
alert(myNewElf.name); //displays Brizza

Of course, JSON Objects are not so easily copied by value, but that is a story for another blog.

Write a comment

You must be logged in to write a comment. If you're not a registered member, please sign up.

February 2012
S M T W T F S
January 2012March 2012
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29