JavaScript Arrays, pass-by-value, and thinking about memory

The first programming language I learned was C++ and in learning it I learned about pass-by-value and pass-by-reference; C++ uses pass-by-value by default for passing arguments into functions. You can specify that you want to pass-by-reference however by putting an ampersand character between the data type and the parameter name in the parameter list in the function definition. JavaScript however gives you no such options, it has one rule to bring them all and in the darkness bind them (arguments to parameters that is, not creatures of Middle-earth to the Dark Lord). Working on an app today I ran into a problem related to this though so let’s shed some light on that darkness of what JavaScript does behind the scenes when passing arguments into a function.


Memory

First of all lets do a quick memory check and enunciate the differences between pass-by-value and pass-by-reference. Variables hold data in memory, but a variable really is just a pointer to a location in memory. By creating a variable you are just telling your program to pick out a space in computer memory and remember it so that you can assign a value to it and retrieve that value when you want.

The ‘reference’ part of pass-by-reference refers to memory locations while the ‘value’ in pass-by-value refers to the actual value in a memory location. So when you use pass-by-value that means any arguments you pass to a function only have their values copied and transferred to the parameters inside the function definition. This means that the parameters inside the function are not the same as the variables you passed in as arguments. They have the same values, but those values are created in new memory locations, so if you change the value of one of those parameters in the function that change does not affect the variable outside the function. There is no connection between argument outside the function and parameter inside the function because all you did was pass the values, hence pass-by-value.

// A pseudo-code example of pass-by-value
function funky(number) {
number = 7;
}
num = 5;
funky(num);
print num; // 5

In the above example the variable num still equals 5 even though the funky function changed it to 7 because the memory location that num points to wasn’t passed into the function, only its value, 5, was passed. So num is not affected. The parameter number is created by the function to point to a new location in memory that holds the value 5 and is then changed to 7.

Pass-by-reference means that you are passing in not the value of the variable but the actual memory location of the variable. You are passing the reference to the value. So in this next example of pass-by-reference the memory location for num is passed into the function so that number is a reference to the exact same memory location. Obviously this means that anything you do to number will also affect num.

// A pseudo-code example of pass-by-reference
function funky(number) {
number = 7;
}
num = 5;
funky(num);
print num; // 7

JavaScript’s pass-by rules

JavaScript follows one rule when it comes to passing arguments into functions, which is to have different rules!

JavaScript always uses pass-by-value, however when the data type of the argument is not a primitive type then the value passed is actually the memory location. So the five primitive types (strings, numbers, booleans, null, and undefined) are pass-by-value as you would expect. But any sort of complex data type (objects and arrays) are pass-by-value with their reference being the value so in effect they are pass-by-reference.

Example of how JavaScript behaves with primitive arguments:

// primitive values are pass-by-value
function funky(number) {
number+= 7;
}
let num = 5;
funky(num);
console.log(num);       // 5

Example of how JavaScript behaves with non-primitive arguments:

// non-primitive values are pass-by-reference
function funky(arr, obj) {
arr.push(4);
obj.food = 'apple pie';
}
let myArr = [1,2,3];
let myObj = { name: 'Tony', food: 'pizza' };
funky(myArr, myObj);
console.log(myArr);       // [1,2,3,4]
console.log(myObj); // { name: 'Tony', food: 'apple pie' }

In the above example you can see an object and an array being pass-by-reference because the outside variables myArr and myObj are changed inside the function. This is because the memory locations of myArr and myObj were passed into the function instead of their array and object values.

Pass-by-reference is nice because it means if you are manipulating multiple arrays or objects in a function you don’t need to return multiple things. However if you practice a no-side effect functional way of programming then this would be bad because your function, by manipulating outside values, is changing the outside state of the application (no-side effects means that only thing that a function results in is its return value). Anyway, that is a discussion for another time.


Tricks of Memory

However this does not mean that you can do anything you want to an object or array and still have its changes affect the original passed-in variable. The reason is memory locations. If you manipulate the individual elements or properties of an array or object then the reference to that variable does not change, therefore the connection between argument and parameter stays intact since they both still point to the same location in memory.

In JavaScript any time you assign a new value to a variable what happens is a new memory location is assigned to that variable with the given value at that location in memory. So assigning an entirely new array to an array parameter in a function makes that parameter point to a new memory location, while the original array variable outside the function still points to the original memory location. Same thing if you assign a new object literal to an object parameter in a function. What this means is that the link (the shared memory location) between outside argument and inside parameter is broken so the changes inside the function will not affect the arguments outside the function.

The last code example kept the references intact because the function directly manipulated the array and object by using the array’s push method to add an element and directly changing a value on a property of the object. The function didn’t make a new array or object, it just changed parts of them — it changed their elements/properties values, not array/object’s references.

In the below example, however, I will show how you can break the reference link between outside argument and inner parameter by assigning a whole new value to the parameter.

// example changing memory locations, thereby breaking reference link
function funky(arr, obj) {
arr = [-1,-2,-3];
obj = { age: 12 };
console.log(arr); // [-1,-2,-3]
console.log(obj); // { age: 12 }
}
let myArr = [1,2,3];
let myObj = { name: 'Tony', food: 'pizza' };
funky(myArr, myObj);
console.log(myArr);       // [1,2,3]
console.log(myObj); // { name: 'Tony', food: 'pizza' }

In the above example when [-1,-2,-3] is assigned to the arr parameter what happens is that JavaScript makes a new memory location for [-1,-2,-3] and assigns that memory location to arr. The memory locations for arr and myArr are now different so arr can no longer affect myArr. Same thing happened between obj and myObj.


My problem

The reason I wrote this article is because I ran into a problem today in the app I’m working on where I expected the array I passed into a function to be changed based on what I did inside the function but I realized sadly that it didn’t. Let’s see what I was doing.

I had an array of user’s ids and a parallel array of their GPS coordinates. I wanted to pass the both of the arrays into the function, along with a max distance and one more pair of GPS coordinates, then figure out the distance between each coordinate pair in the array and the stand alone pair of coordinates. The function would filter out users from the user array that were further away than the max distance, and create an array of distances to match the users that were left in the array. This new distance array was returned from the function and assigned to an array outside the function to represent the distances each user was from the stand alone pair of coordinates.

Basically I wanted to filter down users by a max distance and return a matching pair of distances for each user.

Crucial to this function was the fact that arrays are passed by reference so the outer user array would get filtered down inside the function. Except I used JavaScript’s array filter method, Array.prototype.filter, which returns a whole new array with a reference to a newly allocated memory location. So inside the function when I assigned the result of the filter to the users array parameter is assigned a new memory location to the array, so the user array outside the function and the user array inside the function no longer pointed to the same memory location. This meant I wasn’t actually getting the filtered array out of the function.

Here is some basic code showing the problem:

userArray;       // filled with user IDs
coordsArray; // filled with coordinates for each user from userArray
myCoords; // has pair of coordinates for this user in lat, lon properties
maxDist; // holds a number
let distArray = convertGpsToMiles(userArray, coordsArray, myCoords, maxDist);
console.log(userArray);       // still holds original userArray unfiltered
function convertGpsToMiles(users, coords, myLocation, maxD) {
let miles;
let distances = [];
users = users.filter((el, index) => {
miles = convertCoordsToDistance(myLocation, coords[i].lat, coords[i].lon);
if (miles <= maxD) {
distances.push(miles);
return true;
} else {
return false;
}
});
return distances;
}

The outer userArray and the function’s users array were separated when I assigned the new filtered array to users with users.filter().


Solution(s)

Before I really started to think about how JavaScript works with memory I tried a few fixes that did not work because none of them were able to assign the new filtered array from users.filter() to the users array in the function.

One solution in pure JavaScript would be to use a standard for-loop instead of the Array.prototype.filter method. In the for-loop, if miles was less than or equal to maxD, I would push miles into the distances array and then use JavaScript’s Array.prototype.splice method to cut out the unwanted users. This way I would just be directly manipulating the array of users instead of assigning a new array to users, so the reference to the memory location would not change. Though due to the splice method changing the length of the array in-place I would have to make sure to manipulate the for-loop counter so as to keep up with the changes made to the array’s length, and this means I would also need to splice out elements at the same indices in the coords array to make the coords array stay in parallel with the users array.

function convertGpsToMiles(users, coords, myLocation, maxD) {
let miles;
let distances = [];
  for (let i=0; i < users.length; i++) {
miles = convertCoordsToDistance(myLocation, coords[i].lat, coords[i].lon);
if (miles <= maxD) {
distances.push(miles);
} else {
users.splice(i, 1);
coords.splice(i, 1);
i--;
}
}
return distances;
}

This would work and be faster than using the Array.prototype.filter method, so if my application became popular and had tons of concurrent users one way I might try to optimize the efficiency is by using this solution.

Since I am using Angular another solution is to use the angular.copy() method with the following syntax: Angular.copy(src, dest)

The angular.copy() method does a deep copy from a source array to a destination array, meaning that it goes in and copies individual elements of the array so the memory location doesn’t change, rather than just pointing the destination array to the source array’s memory location or copying the entire source array (like what Array.prototype.slice would do with no arguments) and pointing the destination array to the new copy’s memory location.

The angular docs describe exactly what it does, it clears the destination array and then copies over the individual elements from the source array:

* @ngdoc function
* @name angular.copy
* @module ng
* @kind function
*
* @description
* Creates a deep copy of `source`, which should be an object or an array.
*
* * If no destination is supplied, a copy of the object or array is created.
* * If a destination is provided, all of its elements (for arrays) or properties (for objects)
* are deleted and then all elements/properties from the source are copied to it.

Since I’m using Angular I went with the Angular solution and used angular.copy(), passing in the filtered array as the source array and my users array parameter as the destination.

function convertGpsToMiles(users, coords, myLocation, maxD) {
let miles;
let distances = [];
angular.copy(users.filter((el, i) => {
miles = convertCoordsToDistance(myLocation, coords[i].lat, coords[i].lon);
if (miles <= maxD) {
distances.push(miles);
return true;
} else {
return false;
}
}), users);
return distances;
}

And it works!


Conclusion

So there you have it, JavaScript passes primitives by value but essentially passes arrays and objects by reference. But you’ve gotta watch out that you don’t reassign an object or array inside a function thereby making it reference a new memory location and breaking the link it has with the original argument. Basically, if you use the assignment operator, =, with a whole array or whole object on the left-hand side then you are assigning an entirely new memory location to that variable. Instead, if you need to preserve the references in a function, you want to directly change properties on objects with the dot operator or use the array methods that manipulate an array in-place (like splice(), push(), etc) rather than those that return a new array (like filter(), map(), slice(), etc).

Happy scripting!