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.