43

I've read many answers here relating to 'by value' and 'by reference' passing for sending arrays to javascript functions. I am however having a problem sending an array to a function and leaving the original array unaltered. This example llustrates the problem:

function myFunction(someArray)
{
// any function that makes an array based on a passed array;
// someArray has two dimensions;
// I've tried copying the passed array to a new array like this (I've also used 'someArray' directly in the code);

funcArray = new Array();
funcArray = someArray;

var i = 0;

    for(i=0; i<funcArray.length; i++)
    {
    funcArray[i].reverse;
    }

return funcArray;

}

I can't understand why anything in this function should alter the original array.

calling this function directly changes the original array if the function call is assigned to a new array:

myArray = [["A","B","C"],["D","E","F"],["G","H","I"]];
anotherArray = new Array();

anotherArray = myFunction(myArray);
// myArray gets modified!;

I tried using .valueOf() to send the primitive:

anotherArray = myFunction(myArray.valueOf());
// myArray gets modified!;

I have even tried breaking the array down element by element and sub-element by sub-element and assigning all to a new 2-d array and the original array still gets modified.

I have also joined the sub-elements to a string, processed them, split them back into arrays and the original array still gets modified.

Please, does any one know how I can pass the array values to a function and not have the passed array change?

Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
Dave Pritlove
  • 2,601
  • 3
  • 15
  • 14
  • Sorry, I forgot to mention that the slice method makes no difference either. I've seen this mentioned and tried it but it does not stop the original array changing (neither does .valueOf() which is supposed to reduce the array content to primitive) – Dave Pritlove Jan 23 '13 at 23:46
  • I've now explored the slice() idea some more and the problem lies in it being a 2-d array. If I slice the outer array, it makes no difference, the original array still gets altered. However, if I slice each sub-array, it works! I set up a loop for each outer element and assigned its slice to the outer element of a new array. – Dave Pritlove Jan 24 '13 at 00:13

9 Answers9

59

Inside your function there's this:

funcArray = new Array();
funcArray = someArray;

This won't actually copy someArray but instead reference it, which is why the original array is modified.

You can use Array.slice() to create a so-called shallow copy of the array.

var funcArray = someArray.slice(0);

Modern versions of ES also support destructuring expressions, which make it look like this:

const funcArray = [...someArray];

The original array will be unaltered, but each of its elements would still reference their corresponding entries in the original array. For "deep cloning" you need to do this recursively; the most efficient way is discussed in the following question:

What is the most efficient way to deep clone an object in JavaScript?

Btw, I've added var before funcArray. Doing so makes it local to the function instead of being a global variable.

Ja͢ck
  • 170,779
  • 38
  • 263
  • 309
  • Thanks, as per my second comment, slice() makes it work but only if applied to each outer element of the 2-d array by looping. – Dave Pritlove Jan 24 '13 at 00:16
  • this should be updated to include the spread syntax way since ES6 came out. (@Nick Parsons answer) – salouri Nov 03 '22 at 12:21
9

Make a copy of the array that you can use.

A simple way to do this is by using var clone = original.slice(0);

Tyler Hughes
  • 469
  • 2
  • 5
4

With ES6, you can use the rest element syntax (...) within a destructuring expression to perform a shallow copy directly in the parameter list, allowing you to keep the original array unaltered.

See example below:

const arr = [1, 2, 3, 4, 5];

function timesTen([...arr]) { // [...arr] shallow copies the array
  for(let i = 0; i < arr.length; i++) {
    arr[i] *= 10; // this would usually change the `arr` reference (but in our case doesn't) 
  }
  return arr;
}

console.log(timesTen(arr));
console.log(arr); // unaltered

The above is useful if you have an array of primitives (strings, booleans, numbers, null, undefined, big ints, or symbols) because it does a shallow copy. If you have an array of objects, or an array of arrays (which are also technically just objects), you will want to perform a deep clone to avoid modifying the array and its references. The way to do that in modern-day JS is to use a structured clone, which helps deal with many of the shortcomings of deep cloning with the JSON.stringify() technique as previously used:

function myFunction(someArray) {
  const funcArray = structuredClone(someArray);
  ...
}
Nick Parsons
  • 45,728
  • 6
  • 46
  • 64
3

What about destructuring assignment (ES6+, check compatibility)? Nice and clean solution.

function myFunction(someArray) {

  for(let i = 0; i < someArray.length; i++)
  {
    someArray[i].reverse();
  }
  
  return someArray;
}

let myArray = [["A","B","C"],["D","E","F"],["G","H","I"]];

// Using destructuring assignment.
// NOTE: We can't just use `[...myArray]` because nested arrays will still be copied by reference.
let anotherArray = myFunction([...myArray.map(nested => [...nested])]);

console.log({original: myArray, copy: anotherArray});
Bardr
  • 2,319
  • 2
  • 12
  • 21
2

A variable pointing to an array is a reference to it. When you pass an array, you're copying this reference.

You can make a shallow copy with slice(). If you want a full depth copy, then recurse in sub objects, keeping in mind the caveats when copying some objects.

alex
  • 479,566
  • 201
  • 878
  • 984
1

If you need to do this with an object, try this fancy trick...

MY_NEW_OBJECT = JSON.parse(JSON.stringify(MY_OBJECT));
doublejosh
  • 5,548
  • 4
  • 39
  • 45
0

A generic solution would be...

// Use the JSON parse to clone the data.
function cloneData(data) {
  // Convert the data into a string first
  var jsonString = JSON.stringify(data);

  //  Parse the string to create a new instance of the data
  return JSON.parse(jsonString);
}

// An array with data
var original = [1, 2, 3, 4];

function mutate(data) {
  // This function changes a value in the array
  data[2] = 4;
}

// Mutate clone
mutate(cloneData(original));

// Mutate original
mutate(original);

This works for objects as well as arrays.

Very effective when you need deep cloning or you don't know what the type is.

Deep cloning example...

var arrayWithObjects = [ { id: 1 }, { id: 2 }, { id: 3 } ];

function mutate(data) {
  // In this case a property of an object is changed!
  data[1].id = 4;
}

// Mutates a (DEEP) cloned version of the array
mutate(cloneData(arrayWithObjects));

console.log(arrayWithObjects[1].id) // ==> 2

Warnings

  • Using the JSON parser to clone is not the most performant option!

  • It doesn't clone functions only JSON supported data types

  • Cannot clone circular references

Split Your Infinity
  • 4,159
  • 1
  • 21
  • 19
0

by default in javascript except objects and arrays, everything is copy-by-value but if you want to use copy-by-value for arrays: use [yourArray].slice(0) and for objects use Object.assign(target, ...sources)

Sandeep Gantait
  • 837
  • 8
  • 9
-1
var aArray = [0.0, 1.0, 2.0];
var aArrayCopy = aArray.concat();
aArrayCopy[0] = "A changed value.";
console.log("aArray: "+aArray[0]+", "+aArray[1]+", "+aArray[2]);
console.log("aArrayCopy: "+aArrayCopy[0]+", "+aArrayCopy[1]+", "+aArrayCopy[2]);

This answer has been edited. Initially I put forth that the new operator handled the solution, but soon afterward recognized that error. Instead, I opted to use the concat() method to create a copy. The original answer did not show the entire array, so the error was inadvertently concealed. The new output shown below will prove that this answer works as expected.

aArray: 0, 1, 2
aArrayCopy: A changed value., 1, 2
  • That code doesn't do what you think, with `new Array(aArray)` you creating a new array in which the first element is `aArray`, not making a copy of the original array, log the full arrays instead of the first element and you'll see the difference. – Alberto Martinez Sep 25 '17 at 22:59
  • Thanks Alberto. You were quick to respond, and correct. Use of `new Array(someArray);` would set the length to 1 and set that first element to the reference of the passed arrray without a proper value assignment per element as expected for a pass-by-value. I have been using Float32Array a lot recently, and had to relearn that the basic array does not construct the same way. My test sample output did not identify the error. Now it correctly results the entire array copy. – Elisha McNutt Sep 25 '17 at 23:42