In JavaScript, and all programming languages, there are three important ways that you can manipulate a data value. First, you can copy it, by assigning it to a new variable, for example. Second, you can pass it as an argument to a function or method. Third, you can compare it with another value to see if the two values are equal. In order to understand any programming language, you must understand how these three operations are performed in that language.
There are two fundamentally distinct techniques in which data values can be manipulated. These techniques are called "by value" and "by reference." When a value is manipulated "by value" it is the value of the datum that matters: in an assignment, a copy of the actual value is made and that copy is stored in a variable or object property or array element; the copy and the original are two totally independent values that are stored separately. When a datum is passed "by value" to a function, a copy of the datum is passed to the function; if the function modifies that value, the change affects only the function's copy of the datum--it does not affect the original datum. And when a datum is compared "by value" to another datum, the two distinct pieces of data must represent exactly the same value (which usually means that a byte-by-byte comparison finds them to be equal).
The other way of manipulating a datum is "by reference." With this technique, there is only one actual copy of the datum, and it is references to that datum that are manipulated.[1] When a datum is manipulated "by reference," there is only ever one copy of the actual value. If a value is manipulated "by reference," then variables do not hold that value directly; they only hold references to it. It is these references that are copied, passed, and compared.
[1] C programmers, and anyone else familiar with the concept of "pointers," will understand the idea of a "reference" in this context. Note, however, that JavaScript does not support pointers.
So, in an assignment made "by reference," it is the reference to the value that is assigned, not a copy of the value, and not the value itself. After the assignment, the new variable will contain the same reference to the value that the original variable contains. Both references are equally valid, and both can be used to manipulate the value--if the value is changed through one reference, that change will also appear through the original reference. The situation is similar when a datum is passed to a function "by reference:" a reference to the value is passed to the function, and the function can use that reference to modify the value itself; any such modifications will be visible outside the function. And finally, when a datum is compared to another "by reference," the two references are compared to see if they refer to the same unique copy of a value; references to two distinct datums that happen to have the same value (consist of the same bytes) will not be treated as equal.
These are two very different ways of manipulating values, and they have very important implications that you should understand. Table 9.2 summarizes these implications. This discussion of manipulating data "by value" and "by reference" has been a general one: the distinctions apply to all programming languages. The subsections that follow explain how they apply specifically to JavaScript--which data types are manipulated by value and which are manipulated by reference.
By Value | By Reference | |
---|---|---|
Copy |
The value is actually copied; there are two distinct, independent copies. |
Only a reference to the value is copied. If the value is modified through the new reference, that change is also visible through the original reference. |
Pass |
A distinct copy of the value is passed to the function; changes to it have no effect outside the function. |
A reference to the value is passed to the function. If the function modifies the value through the passed reference, the modification is visible outside the function. |
Compare |
Two distinct values are compared (often byte by byte) to see if they are the same value. |
Two references are compared to see if they refer to the same value. Two references to distinct values are not equal, even if the two values consist of the same bytes. |
The basic rule in JavaScript is this: primitive types are manipulated by value, and reference types, as the name suggests, are manipulated by reference. Numbers and Booleans are primitive types in JavaScript--primitive because the consist of nothing more than a small fixed number of bytes, bytes that are very easily manipulated at the low (primitive) levels of the JavaScript interpreter. On the other hand, objects and arrays are reference types. These data types can contain arbitrary numbers of properties or elements, and so can be of arbitrary size, and cannot be so easily manipulated. Since object and array values can become quite large, it doesn't make sense to manipulate these types by value, which could involve the inefficient copying and comparing of large amounts of memory.
What about strings and functions? These types may have arbitrary length, and so it would seem that they would be reference types. In fact, though, they are usually considered to be primitive types in JavaScript, simply for the reason that they are not objects or arrays. Strings and functions do not follow the "primitive types by value and reference types by reference" rule presented above, and will be discussed in a section of their own later in this chapter.
Examples using primitive and reference types are the best way to explore the differences between data manipulation by value and data manipulation by reference. Study the following examples carefully, paying attention to the comments. First, Example 9-1 copies, passes, and compares numbers. Since numbers are primitive types, this illustrates data manipulation by value.
// First we illustrate copy by value. n = 1; // variable n holds the value 1 m = n; // copy by value: variable m holds a distinct value 1 // Here's a function we'll use to illustrate pass-by-value. // As we'll see, the function doesn't work the way we'd like it to. function add_to_total(total, x) { total = total + x; // this line only changes the internal copy of total } // Now call the function, passing the numbers contained in n and m by value. // The value of n is copied, and that copied value is named total within the // function. The function adds a copy of m to that copy of n. But adding // something to a copy of n doesn't affect the original value of n outside // of the function. So calling this function doesn't accomplish anything. add_to_total(n, m); // Now, we'll look at comparison by value. // In the line of code below, the literal 1 is clearly a distinct numeric // value encoded in the program. We compare it to the value held in variable // n. In comparison by value, the bytes of the two numbers are checked to // see if they are the same. if (n == 1) m = 2; // n contains the same value as the literal 1
Next, consider Example 9-2. This example copies, passes, and compares an object. Since objects are reference types, these manipulations are performed "by reference." The example uses Date objects, which you can read about in the reference section of this book, if necessary.
// Here we create an object representing the date of Christmas, 1996. // The variable xmas contains a reference to the object, not the object itself. xmas = new Date(96, 11, 25); // When we copy by reference, we get a new reference to the original object. solstice = xmas; // both variables now refer to the same object value // Here we change the object through our new reference to it solstice.setDate(21); // The change is visible through the original reference, as well. xmas.getDate(); // returns 21, not the original value of 25 // The same is true when objects and arrays are passed to functions. // The following function adds a value to each element of an array. // A reference to the array is passed to the function, not a copy of the array. // Therefore, the function can change the contents of the array through // the reference, and those changes will be visible when the function returns. function add_to_totals(totals, x) { totals[0] = totals[0] + x; totals[1] = totals[1] + x; totals[2] = totals[2] + x; } // Finally, we'll examine comparison by value. // When we compare the two variables defined above, we find they are // equal, because the refer to the same object, even though we were trying // to make them refer to different dates: (xmas == solstice) // evaluates to true // The two variables defined below refer to two distinct objects, both // of which represent exactly the same date. xmas = new Date(96, 11, 25); solstice_plus_4 = new Date(96, 11, 25); // But, by the rules of "compare by reference," distinct objects not equal! (xmas != solstice_plus_4) // evaluates to true
Before we leave the topic of manipulating objects and arrays by reference, there is a point about passing values by reference that it is important to get straight. When an object is passed to a function, it is a reference to the object that is passed, not a copy of the object's actual value. As we've seen in Example 9-2 this means that we can modify the object's value through the reference, and these modifications will be visible when the function returns. What we cannot do, and this is where confusion can arise, is modify the reference itself. The function is passed a copy of the reference to the object (in a sense, the reference itself is "passed by value"). If the function changes its copy of the reference, that change does not affect the object value nor the original reference to the object, and the change will not be visible outside of the function. Example 9-3 illustrates this.
// This is another version of the add_to_totals() function. It doesn't // work, through, because instead of changing the array itself, it tries to // change the reference to the array. function add_to_totals2(totals, x) { newtotals = new Array(3); newtotals[0] = totals[0] + x; newtotals[1] = totals[1] + x; newtotals[2] = totals[2] + x; totals = newtotals; // this line has no effect outside of the function. }
Note that this rule applies not only to pass-by-reference, but also copy-by-reference. You can modify an object through a copy of a reference, but changing the copied reference itself does not affect the object nor the original reference to the object. This is a more intuitive and less confusing case, so we don't illustrate it with an example.
As mentioned in the previous section, strings and functions in JavaScript don't fit neatly into the primitive-type versus reference-type dichotomy. For most purposes, strings and functions are considered primitive types by default--because they are not objects or arrays. If they are primitive types, then by the rules given above, they should be manipulated by value. But since a string can be arbitrarily long, and a function can contain an arbitrary amount of JavaScript code, these types do not have a fixed size, and it would be inefficient to copy, pass, and compare these data types byte by byte.
Since it is unclear whether JavaScript copies and passes strings and functions by value or by reference, we can try to write some JavaScript code to experiment with these data types. If they are copied and passed by reference, then we should be able to modify the contents of a string or function value through a copy of the value or a through a function that takes the value as an argument. When we set out to write the code to perform this experiment and determine whether strings and functions are copied and passed by reference, we run into a major stumbling block: there is no way to modify the contents of a string or a function. We can modify the contents of an object or an array by setting object properties or array elements. But strings and functions are immutable in JavaScript--that is, there is no JavaScript syntax, or JavaScript functions, methods, or properties that allow you to change the characters in the string or the code in the function.
Since strings and functions are immutable, our original question is moot: there is no way to tell if strings and functions are passed by value or by reference. Because of efficiency considerations, we can assume that JavaScript is implemented so that strings and functions are passed by reference, but in actuality it doesn't matter, since it has no practical bearing on the code we write.
Despite the fact that we cannot determine whether strings and functions are copied and passed by value or by reference, we can write JavaScript code to determine whether they are compared by value or by reference. Example 9-4 shows the code we might use to make this determination.
// Determining whether strings are compared by value or reference is easy. // We compare two clearly distinct strings that happen to contain the same // characters. If they are compared by value they will be equal, but if they // are compared by reference, they will not be equal: s1 = "hello"; s2 = "hell" + "o"; if (s1 == s2) document.write("Strings compared by value"); // Determining whether functions are compared by value or reference is trickier, // because we cannot define two functions with the same name. Therefore, we // have to use unnamed functions. Don't feel you have to understand this code. // We create two distinct functions that contain exactly the same code. // If JavaScript says these two functions are equal, then functions are // compared by value, otherwise they are compared by reference. F = new Function("return 1;"); // F and G are Function objects that contain G = new Function("return 1;"); // unnamed function values. f = F.valueOf(); // convert F and G to the actual function values g = G.valueOf(); if (f == g) // now compare them document.write("Functions compared by value");
The results of this experiment are surprising. Strings are compared by value, and functions are compared by reference. The fact that strings are compared by value may be counter-intuitive to C, C++, and Java programmers--in those languages, strings are reference types, and you must use a special function or method when you want to compare them by value. JavaScript, however, is a higher-level language, and recognizes that when you compare strings you almost always want to compare them by value. Thus, as a special case, it compares strings by value even though they are (presumably) copied and passed by reference.
The fact that functions are compared by reference is quite reasonable. Since it doesn't make sense to write two separate functions that do exactly the same thing, we never really want to compare functions by value. Comparing functions by reference is far more useful.
We've seen above that objects are copied by reference. There is one exception to this rule, however. If the left-hand side of an assignment expression refers to an object, and that object has an assign() method, then instead of copying a reference to the right-hand value into the left-hand variable, as usual, the assign() method is called instead, with the value of the right-hand side as its argument. You can define this method so that an assignment performs any sort of action you desire. Example 9-5 shows how you can use this feature to override the "copy-by-reference" nature of an object. The assign() method is also covered in detail in Chapter 7, Objects.[2]
[2] Note that the assign() method is not supported in Internet Explorer 3.0, and may not be supported in future versions of Navigator.
// This is the function we'll use for the assign() method. function myassign(rhs) { var i; for (i in rhs) this[i] = rhs[i]; } myobject = new Object; // create an object myobject.assign = myassign; // set the custom assign() method on it // Now, when an object is assigned to "myobject", the properties // of that object are copied, rather than overwriting the "myobject" // variable with a reference to the other object. myobject = my_other_object; // After the above assignment, myobject and my_other_object still refer // to two separate objects, but myobject has a copy of each of the // properties of my_other_object.
The sections above have been quite detailed and perhaps somewhat confusing. Table 9.3 summarizes these sections.
Copied | Passed | Compared | |
---|---|---|---|
Number | By value | By value | By value |
Boolean | By value | By value | By value |
Object | By reference (or assign() method) | By reference | By reference |
Array | By reference (or assign() method) | By reference | By reference |
String | Immutable (by reference) | Immutable (by reference) | By value |
Function | Immutable (by reference) | Immutable (by reference) | By reference |