Understanding JavaScript reference versus instance
AKA Why did my entire array change values inexplicably???
The Problem
Let's just say you're taking a break from spending 17 hours per day on Codewars on your holy quest to become an 8-Dan supreme master. Say you are trying to create a gameboard for a Battleship project. Say you are trying to do this with:
const board = Array(10).fill(Array(10).fill(0));
Don't... just don't...
What will happen here is you will seemingly have the board you wanted, an array of 10 filled with nested arrays of 10 zeros:
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]];
You would then assume you can just change some of the values with:
board[0][0] = 'battleship';
You would assume wrong. Every single one of the subarrays will now be set to 'battleship' at index 0 and this could be very difficult to debug without understanding what's going on.
But Why
What happened here with Array(10).fill(Array(10).fill(0))
?
Well, .fill is evaluated one time, so each "array" is not a separate array, but is actually the same thing - that one array instance. They are all different references to the same array instance. See this also.
The Solution
Use a method that will actually create separate array instances for each element in board, perhaps:
let board= Array(10);
for(let i = 0; i < board.length; i += 1) {
board[i] = Array(10).fill(0);
}
A Similarly Counterintuitive Example
A similar situation could occur if you have a factory with a method that changes some variable, but you have only returned the reference to a variable, rather than another method/function that gets the current actual state of the variable. Consider this:
const player = () => {
let health = 100;
const hit = (damage) => {
health -= damage;
}
return {health};
}
Counterintuitively, health does not represent the current value of the health property of the player object. It is a reference to the value (which was 100) right after it was created in the player object and subsequently returned.
If the player is attacked, the value of health should change. This could lead to an incorrect value of health always being 100, because it is pointing to the snapshot of the variable at the time it was created, rather than the current state of it on the player object.
Another Solution
To fix this, we would need a method that gets the actual current value of health:
const player = () => {
let health = 100;
const hit = (damage) => {
health -= damage;
}
const getHealth = () => {
return health;
}
return {getHealth};
}
Reference vs Instance
An instance of an object is an object that has been created and exists in memory. Reference to an object is something (like a variable) that points to an instance, allowing us to access it. See this link
In JavaScript, primitives (string, number, bigint, boolean, undefined, symbol, and null) are passed by actual values. For example, let name = 'Sal';
creates a space in memory to store the value 'Sal'. And let person = 'Sal';
makes a different space in memory for the value 'Sal'. Changing the value of name won't affect person, since they are at different memory locations.
Arrays, objects, and functions do not operate the same way. Multiple variables can point to the same array, object, or function. For example, after const people = ['hal', 'bob', 'sal'];
and const persons = people;
, both people and persons are referring/pointing to the same array object at the one same location in memory. Changing either would change the array.