0

I created a constant named 'alphabet' and assigned it to an array containing the first 5 letters of the alphabet. I then wanted to have a function that would add a number to each character in the array, to enumerate them. But, I didn't want the values in the original 'alphabet' array to be modified, so I created a 'temp' variable within my enumerating function and only made changes to that. However, any changes I made to 'temp' spread over to 'alphabet'. I don't understand why and I'd like to prevent that from happening.

Here's my code: (also available on CodePen)

const alphabet = ["a", "b", "c", "d", "e"];

function alphaPosition(seq) {
  //'temp' gets 'seq' to avoid making changes directly on the provided argument.
  let temp = seq;
  //adds indexes to each element in the 'temp' array:
  for (let i = 1; i <= temp.length; i++) {
    temp[i - 1] = temp[i - 1] + i;
  }
  return temp;
}

console.log(
  "Step 1. 'alphabet' array before running the 'alphaPosition' function:"
);
console.log(alphabet);
console.log(
  "Step 2. This is the final value of 'temp' in 'alphaPosition' after running the function. An index has been added to every element in the array, as expected:"
);
console.log(alphaPosition(alphabet));
console.log(
  "Step 3. Here's the 'alphabet' array after running 'alphaPosition'. Indexes have also been added to every element, despite not modifying the function argument directly:"
);
console.log(alphabet);

Output:

/*
-> Step 1. 'alphabet' array before running the 'alphaPosition' function:
-> ["a", "b", "c", "d", "e"]
-> Step 2. This is the final value of 'temp' in 'alphaPosition' after running the function. An index has been added to every element in the array, as expected:
-> ["a1", "b2", "c3", "d4", "e5"]
-> Step 3. Here's the 'alphabet' array after running 'alphaPosition'. Indexes have also been added to every element, despite not modifying the function argument directly:
-> ["a1", "b2", "c3", "d4", "e5"]
*/

Why do changes to 'temp' spread to 'alphabet'? I would expect that, since I defined 'alphabet' as a constant, it shouldn't even be possible to modify it. Also, I never make changes to it in my function. I only use it to define 'temp'. Is there any way to prevent these spreads from happening?

I experimented doing something similar with a numeric const instead of an array, and everything worked as intended:

const number = 10;

function numChange(n) {
  //'virtualN' gets 'n' to avoid making changes directly on the provided argument.
  let virtualN = n;
  //modify 'virtualN' multiple times to emulate what was done to the 'temp' array in the alphaPosition function.
  for (let i = 1; i <= 5; i++) {
    virtualN = "iteration" + i;
  }
  return virtualN;
}

console.log(
  "Step 1. See the value of 'number' before running the numChange function:"
);
console.log(number);
console.log(
  "Step 2. This is the final value of 'virtualN' in 'numChange(number)' after running the function. As expected, it's been modified from its initual value by the 'for' loop:"
);
console.log(numChange(number));
console.log(
  "Step 3. Finally, we can see the value of 'number' is still the same as before running the numChange function. As expected, only the value of virtualN changed while the argument 'n' suffered no modifications:"
);
console.log(number);

Output:

/*
-> Step 1. See the value of 'number' before running the numChange function:    
-> 10     
-> Step 2. This is the final value of 'virtualN' in 'numChange(number)' after running the function. As expected, it's been modified from its initual value by the 'for' loop:  
-> iteration5
-> Step 3. Finally, we can see the value of 'number' is still the same as before running the numChange function. As expected, only the value of virtualN changed while the argument 'n' suffered no modifications:
-> 10
*/

Why does using an intermediary variable to avoid making changes to the original work for numbers and not for arrays?

I'd be extremely grateful for any help or clarification on this. I've added my code to this CodePen in case you'd like to tweak it or experiment more with it. Thank you in advance for any help.

M2K
  • 319
  • 3
  • 11
  • `let temp = seq;` doesn't create a copy because `seq` doesn't contain a primitive value like `3` but a reference to an object in memory. (also note that declaring an array as constant doesn't mean you can't push to it or change its elements, you just can't overwrite what the const actually stores: the reference to the memory object) –  Jun 03 '20 at 00:28
  • Duplicate: [Modifying a copy of a JavaScript object is causing the original object to change](https://stackoverflow.com/questions/29050004/modifying-a-copy-of-a-javascript-object-is-causing-the-original-object-to-change) –  Jun 03 '20 at 00:29
  • Arrays and object assignment will still reference the original variable. Strings and numbers are primitive data structures, so assignment is a value, not a reference. You need to make a copy of an array or object before assigning it to a new variable. – elisecode247 Jun 03 '20 at 00:30

2 Answers2

0

This is about how assignment works in javascript. Given the following code:

const a = { foo: "bar" };
const b = a;

Variables a and b both point to the same object. This means there is only one object in memory and mutating the object a points to will be reflected when you try to access the object using b and vice versa. For example:

const a = { foo: "bar" };
const b = a;

a.foo = "baz";

console.log(a);
console.log(b);

So now, how do we make this not happen? We can do this by assigning a shallow copy of a to b. This can be done a few different ways, the following uses the spread operator (...):

const a = { foo: "bar" };
const b = { ...a };

a.foo = "baz";

console.log(a);
console.log(b);

So now, there are two different objects in memory. You'll notice I called this a shallow copy, which is important: if you have deep objects you'll need to copy deeply to accomplish the same kind of decoupling.

How to fix this in your specific case:

The TLDR for your specific case is that your temp variable should be a shallow copy of the array, not a reference to the existing array:

let temp = [...seq];
Nick
  • 16,066
  • 3
  • 16
  • 32
  • Thank you so much, @Nick! Replacing `let temp = seq;` with `let temp = [...seq];` did the trick. I first tried using braces `{` `}` instead of brackets `[` `]` (as in your example) to create the duplicate, but I got `{0: "a", 1: "b", 2: "c", 3: "d", 4: "e"}` instead of the intended `["a1", "b2", "c3", "d4", "e5"]`. I'd never seen an array (or object?) format like that, and I couldn't even access the numbers behind the semicolons with the indexing brackets `temp[1]`, etc. What format is that? I don't seem to be able to control it like an array. Could you please point me in the right direction? – M2K Jun 03 '20 at 01:04
  • Yeah sure, `{}` is an object, which allows for key-value pairs. `[]` is an array, which is number-indexed. Technically, they're both just different types of "objects" in JavaScript – Nick Jun 03 '20 at 01:49
  • Ah, got it! Thank you very much for the explanation @Nick. – M2K Jun 04 '20 at 22:36
0

This seems to be a duplicate of this. and the underlying answer is here.

I explain why below:

It would seem that for Strings and numbers, javascript is pass by value, and objects such as arrays is pass by reference.

What this means is that in your first example; the function is taking a reference to the original array, and when you do let temp = seq, temp is actually just a pointer to the original object passed in. In this case that is alphabet so when you modify temp; it is actually modifying alphabet.

Pass by value just sends to the function the value, so the original variable stays the same as in your number example.

In order to get your intended outcome you would want to make a Deep Copy of your array like let temp = deepCopy(seq).

Use of const I think may just be syntax for the user to know not to modify it and some editors will not let you remodify const in the code, but in this case its happening in a roundabout way.

xPaillant
  • 126
  • 6
  • Thank you for your response, @xPaillant! I don't know who downvoted you but your explanation was helpful to me. Thanks! – M2K Jun 03 '20 at 01:12
  • Just one clarification, though, when I run deepCopy(seq) it returns a 'deepCopy()' is not defined error. I suppose you meant that snippet just as an illustration of what I'd have to do, right? – M2K Jun 03 '20 at 01:26
  • @Marcos, glad you found it helpful! deepCopy isn't actually a function , but rather you will need to create your own function called deepCopy, using the links i posted. – xPaillant Jun 04 '20 at 02:11
  • Perfect! Thank you for the clarification. – M2K Jun 04 '20 at 22:33