Variable or reference?

One thing that’s tripped me up recently, and is the cause of some bugs in Sylvester that I’m ironing out, is JavaScript’s handling of array and object variables. When you create an array — [3,4,5] — or an object — {foo: 12, bar: 'something'}, the variable name you give it is not a storage vessel for the value you’re assigning, it’s a direct reference to the value in memory. For example, with strings you can do this:

var foo = 'a string';
var bar = foo;
bar = 'something else';

// foo is still 'a string'

So by saying bar = foo, you’re telling the compiler, “take the value stored in the variable foo and copy it into the variable bar“. The value ‘a string’ is stored in memory twice — once for each variable, until bar is reassigned.

Not so with arrays. Take the following:

var foo = ['apples', 'oranges', 'pears'];
var bar = foo;
bar[2] = 'kiwis';

// foo is now ['apples', 'oranges', 'kiwis']

Now when you say bar = foo, you are not copying foo‘s value into the space in memory represented by bar. There remains only one copy of the array in memory, and both foo and bar are references to it. If you want to copy an array, you need to use the following:

var foo = ['apples', 'oranges', 'pears'];

var bar = foo.slice();
bar[2] = 'kiwis';

// foo is ['apples', 'oranges', 'pears']

The reference vs. value, um, thing (I won’t call it a problem as there are some very useful things you can do with it) also means that any function that takes an array or an object as an argument receives the original variable you pass to it, not a copy of its value. Take a look at the following:

function doSomethingTo(arg) {
  var localArg = arg;
  localArg.push('extra!');
}

var foo = ['this', 'is', 'an', 'array'];
doSomethingTo(foo);

// foo is now ['this', 'is', 'an', 'array', 'extra!']

You might be fooled into thinking that assigning the incoming argument to a local variable and pushing values onto that means that the argument you pass will not be modified, but this is not the case. To leave the argument untouched by the function call, you need to explicitly copy its value in memory using localArg = arg.slice() or by looping through its elements.

The place where this specifically comes up in Sylvester is allowing methods to accept both Vector objects (with an elements property) and arrays as arguments. Some methods need to modify the element array after grabbing it from the argument, and the effect of this depends on what you’re trying to do.

function vectorMethod(vector) {
  var V = vector.elements || vector;
  V.push(10);
  V = Vector.create([3,4,5]);
}

var foo = $V([9,8,7]);
vectorMethod(foo);

// foo is $V([9,8,7,10]);

A fairly useless method, but it illustrates the point. The first line just grabs the elements propery if it exists, otherwise it grabs the raw argument. In the second line, V is thus a reference to the array fed into the function. We call push(10) on this reference, which means we push 10 onto the one copy of the array living in memory, so foo mirrors this change. In the third line, V is reassigned. It is no longer a reference to the argument, but a reference to the newly created object — Vector.create([3,4,5]). This does not assign this new object to the function’s argument.

To sum up, then: array/object variables do not store values, they refer to them. Calling methods on these references modifies the return value of all connected references, but reassigning to references leaves all connected variables unchanged.

If you've enjoyed this article, you might enjoy my recently published book JavaScript Testing Recipes. It's full of simple techniques for writing modular, maintainable JavaScript apps in the browser and on the server.