104

This is also referred to as "deep copying", which I've found some articles on. Closest seems to be this one but it's for jQuery - I'm trying to do this without a library.

I've also seen, in two places, that it's possible to do something like:

arr2 = JSON.decode(JSON.encode(arr1));

But that's apparently inefficient. It's also possible to loop and copy each value individually, and recurs through all the arrays. That seems tiring and inefficient as well.

So what's the most efficient, non-library way to copy a JavaScript multi-dimensional array [[a],[b],[c]]? I am completely happy with a "non-IE" method if necessary.

Thanks!

Machavity
  • 30,841
  • 27
  • 92
  • 100
Randy Hall
  • 7,716
  • 16
  • 73
  • 151
  • How efficient do you need it to be? Are you doing this over and over again in the client (or is this server side like Node)? The JSON stringify -> parse method is very slick even if not the most efficient. – Michael Berkowski Dec 07 '12 at 03:32
  • Otherwise, deep-copy means recursive looping... – Michael Berkowski Dec 07 '12 at 03:32
  • What types of data will your structure hold? Is it just Arrays, or other Objects as well? Is it known how deep your structure goes? – I Hate Lazy Dec 07 '12 at 03:36
  • 1
    ...also any circular references to deal with? – I Hate Lazy Dec 07 '12 at 03:39
  • 1
    I would be sure to benchmark alternatives against json decode+encode. It may seem lame to make strings only to decode them, but it's done in native optimized code- and in the end that may make it faster. – goat Dec 07 '12 at 03:44
  • It's going to happen inside another loop, where I'm creating elements and attaching single pieces of the arrays (for instance, `elem.myparam = arr[0][0]`, which is itself an array). And client side. So "most efficient possible" is nice. The array mostly holds other arrays, integers, strings, and the occasional function. No objects. – Randy Hall Dec 07 '12 at 03:55
  • @MichaelBerkowski the problem I'm running in to is that in certain cases, one of the values in the array piece that I'm "attaching" needs to be changed, and if I change it, the array referenced in changed, and I need it to not be, as I'll need it again later. – Randy Hall Dec 07 '12 at 03:56
  • @IHateLazy no circular references to deal with, luckily. – Randy Hall Dec 07 '12 at 03:57
  • @RandyHall: So if I understand, the structure depth is no more than a single Array of Arrays (like you show in your question)? – I Hate Lazy Dec 07 '12 at 03:57
  • Ehhhhh not EXACTLY. I only have to deal with a single Array of Arrays at a time in my implementation (reference is fine for further children until they are called later, at which time I would only need to "copy" that one other array), but this could be infinite levels deep. – Randy Hall Dec 07 '12 at 04:01
  • 1
    @RandyHall: Ah, I think I understand. If you're only really dealing with them one level deep at any given time, and if they're actual Arrays, then I'd just iterate the current Array and and build a new one using `.slice()` on its nested Arrays. It'll be extremely fast. – I Hate Lazy Dec 07 '12 at 04:02
  • @IHateLazy I've never properly comprehended what `.slice()` does in copying arrays, do you have a cheap and dirty example? And this might be good as an answer =) – Randy Hall Dec 07 '12 at 04:04
  • @IHateLazy: For avoiding circular references, see [this question](http://stackoverflow.com/q/10728412/1048572) – Bergi Dec 07 '12 at 04:05
  • @RandyHall: Sure, I had an answer ready a while ago, so I'll just go ahead and post it. But `.slice()` just basically creates a new array with the same contents, so you'd manually create a new Array, then iterate the old one and slice the nested Arrays. Then when it comes time to go to the next level, you'd do the same. – I Hate Lazy Dec 07 '12 at 04:07
  • I'll give you guys a little sneak peak for being such good sports. It currently only appears to work in Chrome (so far that I've tested), but eh. It's kinda cool. It's currently operating using a loop to assign individual values of said array. Check it http://zerofaction.com – Randy Hall Dec 07 '12 at 04:08
  • @IHateLazy Coming to a GitHub near you (in a couple weeks lol). – Randy Hall Dec 07 '12 at 04:13
  • 1
    If you are not dealing with objects as elements (or are not interested in copying them), you can go: `matrix.map((row) => [...row]);` – Nicolás Fantone Aug 29 '17 at 10:38

5 Answers5

126

Since it sounds like you're dealing with an Array of Arrays to some unknown level of depth, but you only need to deal with them at one level deep at any given time, then it's going to be simple and fast to use .slice().

var newArray = [];

for (var i = 0; i < currentArray.length; i++)
    newArray[i] = currentArray[i].slice();

Or using .map() instead of the for loop:

var newArray = currentArray.map(function(arr) {
    return arr.slice();
});

So this iterates the current Array, and builds a new Array of shallow copies of the nested Arrays. Then when you go to the next level of depth, you'd do the same thing.

Of course if there's a mixture of Arrays and other data, you'll want to test what it is before you slice.

I Hate Lazy
  • 47,415
  • 13
  • 86
  • 77
  • I guess that map with its extra function invocations is much slower in the most browsers, though it could be optimized by inlining and then might perform better. – Bergi Dec 07 '12 at 04:15
  • @Bergi: Yeah, the map version will take a small hit, but it's nice and clean. – I Hate Lazy Dec 07 '12 at 04:16
  • 2
    Wow, the map is really clean – Dan Tang Jun 01 '14 at 10:45
  • Currently we just need to call `.slice(0)` to clone an array. Use `.slice()` is ok but `.slice(0)` is faster. – Trung Le Nguyen Nhat Oct 04 '16 at 08:22
  • 7
    nowadays you can do es6 shorthand making it even more concise. ```var newArray = currentArray.map(arr => arr.slice());``` – Matt Pengelly Nov 22 '18 at 02:51
  • 3
    This is NOT "deep" cloning!! - only the 2nd level deep gets copied by value. All the other, deeper levels are still copied by reference and will still change if the original object/array changes. If you want a thoroughly cloned object, JSON parsing is a much safer bet. – techexpert Jan 30 '20 at 17:02
  • Damn - even though I know better, I was still trying to do `toplevelarray.slice()`. This was uber helpful. – dgo Dec 14 '20 at 19:17
27

I'm not sure how much better JSON.stringify and JSON.parse than encode and decode, but you could try:

JSON.parse(JSON.stringify(array));

Something else I found (although I'd modify it a little):

http://www.xenoveritas.org/blog/xeno/the-correct-way-to-clone-javascript-arrays

function deepCopy(obj) {
  if (typeof obj == 'object') {
    if (isArray(obj)) {
      var l = obj.length;
      var r = new Array(l);
      for (var i = 0; i < l; i++) {
        r[i] = deepCopy(obj[i]);
      }
      return r;
    } else {
      var r = {};
      r.prototype = obj.prototype;
      for (var k in obj) {
        r[k] = deepCopy(obj[k]);
      }
      return r;
    }
  }
  return obj;
}
Magne
  • 16,401
  • 10
  • 68
  • 88
Ian
  • 50,146
  • 13
  • 101
  • 111
  • The downside to both of these methods is that they don't handle self-referential cycles - something that may or may not be important (not sure what the JSON version will do with it, but the second example will infinitely loop) – Jeff Dec 07 '12 at 03:43
  • @Jeff I trust you, but can you give a (small) example? Just wanted to understand – Ian Dec 07 '12 at 03:50
  • 1
    `var x = {}; x.y = x;` will hit the `else` statement every time `deepCopy` is called, and the JSON just plain can't handle self-references - it's innately tree-based – Jeff Dec 07 '12 at 03:51
  • I'm not saying your answer is a bad one, (fine if you know you don't have self-references) but it's not completely general – Jeff Dec 07 '12 at 03:53
  • 1
    What is `r.prototype = obj.prototype;` supposed to do? Seems very wrong to me – Bergi Dec 07 '12 at 03:54
  • @Bergi looks like is copies the prototype functions in one go. This type of looping function seems to be the most popular solution, but I was HOPING that newer browsers had some hidden gem for this type of thing. – Randy Hall Dec 07 '12 at 03:58
  • @Bergi Actually, that's nasty. The prototype isn't iterated over in the for loop (since it's nonenumerable) so it's copied since it (as an object in its own right) is copied - but not deep copied! However you might not want it to be deep copied, since it's the prototype. Tricksy... – Jeff Dec 07 '12 at 04:02
  • 1
    @ RandyHall, Jeff: No, it does not. It just creates a property named `prototype`. This does not set inheritance, as [`__proto__`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/proto) would do. Proper way: `Object.create(Object.getPrototypeOf(obj));` as in @Danny's answer – Bergi Dec 07 '12 at 04:02
  • @RandyHall Ahh I see...and as my browser just experienced the infinite loop. But I know this wouldn't be the best answer, I just wanted to post some "solutions". I seriously found like 5 more "solutions" from Google, and don't know which would be best at all. But since the OP has helped specify exactly what they need, you guys have figured out a better solution. – Ian Dec 07 '12 at 04:15
  • 1
    @Bergi Yeah, I won't lie, I copied and pasted from the link I provided without thinking. I started looking at the code and realized I would change several things (yet I overlooked the `prototype` part). I wish I understood more about prototypes and constructors, to understand more, but thanks for the right way to copy the prototype. – Ian Dec 07 '12 at 04:18
6

As you asked for performance, I guess you also would go with a non-generic solution. To copy a multi-dimensional array with a known number of levels, you should go with the easiest solution, some nested for-loops. For your two-dimensional array, it simply would look like this:

var len = arr.length,
    copy = new Array(len); // boost in Safari
for (var i=0; i<len; ++i)
    copy[i] = arr[i].slice(0);

To extend to higher-dimensional arrays, either use recursion or nested for loops!

The native slice method is more efficient than a custom for loop, yet it does not create deep copies, so we can use it only at the lowest level.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
3

Any recursive algorithm that doesn't visit the same node twice will be about as efficient as you get with javascript (at least in a browser) - in certain situations in other languages you might get away with copying chucks of memory, but javascript obviously doesn't have that ability.

I'd suggest finding someone who's already done it and using their implementation to make sure you get it right - it only needs to be defined once.

Jeff
  • 12,555
  • 5
  • 33
  • 60
0

You could create a copy of multi-dimensional array in JavaScript by using modern way structuredClone:

var arr1 = [[1,2],[3,4],[5,6]];
var arr2 = structuredClone(arr1); //copy arr1
arr2.push([7,8]);

console.log(arr1); //[[1,2],[3,4],[5,6]]
console.log(arr2); //[[1,2],[3,4],[5,6],[7,8]]
XMehdi01
  • 5,538
  • 2
  • 10
  • 34