An object is an unordered collection of data, including primitive types, functions, and even other objects. The utility of objects is that they gather all the data and logic necessary for a particular task in one place. A String object stores textual data and provides many of the functions you need to operate upon it. While objects aren’t strictly necessary in a programming language (for example, C has no objects), they definitely make a language that contains them easier to use.
An object is created with a constructor, a special type of function that prepares a new object for use by initializing the memory it takes up. In Chapter 4, we saw how objects are created by applying the new operator to their constructors. This operator causes the constructor to which it is applied to create a brand-new object, and the nature of the object that is created is determined by the particular constructor that is invoked. For example, the String() constructor creates String objects while the Array() constructor creates Array objects. This is actually the way object types are named in JavaScript: after the constructor that creates them.
A simple example of object creation is
var city = new String();
This statement creates a new String object and places a reference to it in the variable city. Because no argument was given to the constructor, city is assigned the default value for strings, the empty string. We could have made the example more interesting by passing the constructor an argument specifying an initial value:
var city = new String("San Diego");
This places a reference to a new String object with the value "San Diego" in city.
Objects and other variables use memory, which is a limited resource for a computer. Because of the potential scarcity of memory, some programming languages force programmers to carefully manage their program’s use of memory. Fortunately, JavaScript isn’t such a language as it hides memory management issues from programmers. When you create objects in JavaScript, the interpreter invisibly allocates memory for you to use. It also “cleans up” after you as well. This language feature is called garbage collection.
Garbage collecting languages like JavaScript keep a watchful eye on your data. When a piece of data is no longer accessible to your program, the space it occupies is reclaimed by the interpreter and returned to the pool of available memory. For example, in the following code, the initially allocated String that references Monet will eventually be returned to the free pool because it is no longer accessible (i.e., the reference to it was replaced by a reference to the object containing the sentence about Dali):
var myString = new String("Monet was a French Impressionist"); // some other code myString = new String("Dali was a Spanish Surrealist");
The exact details of how the interpreter carries out garbage collection are not really important. However, if your code involves large amounts of data, giving the interpreter hints that you are done with specific variables can be useful in keeping the memory footprint of your script to a reasonable level. An easy way to do this is to replace unneeded data with null, indicating that the variable is now empty. For example, supposing you had a Book object:
var myBook = new Book(); // Assign the contents of War and Peace to myBook // Manipulate your data in some manner // When you are finished, clean up by setting to null myBook = null;
The last statement indicates unequivocally that you are finished with the data referenced by myBook and therefore the many megabytes of memory it took up may be reused.
Note |
If you have multiple references to the same data, be sure that you set them all to null; otherwise, the interpreter keeps the data around in case you need it again. |
A property of an object is some piece of named data it contains. As discussed in Chapter 4, properties are accessed with the dot (.) operator applied to an object. For example,
var myString = new String("Hello world"); alert(myString.length);
accesses the length property of the String object referenced by myString.
Accessing a property that does not exist results in an undefined value:
var myString = new String("Hello world"); alert(myString.noSuchValue);
In Chapter 4 we also saw how it’s easy to use instance properties, properties added dynamically by script:
var myString = new String("Hello world"); myString.simpleExample = true; alert(myString.simpleExample);
Instance properties are so-named because they are only present in the particular object or instance to which they were added, as opposed to properties like String.length, which are always provided in every instance of a String object. Instance properties are useful for augmenting or annotating existing objects for some specific use.
Note |
JavaScript does provide the ability to add a property to all instances of a particular object through object prototypes. However, prototypes are a considerably more advanced language feature and will be discussed along with the details of JavaScript’s inheritance features in a later section in this chapter. |
You can remove instance properties with the delete operator. The following example illustrates the deletion of an instance property that we added to a String object:
var myString = new String("Hello world"); myString.simpleExample = true; delete myString.simpleExample; alert("The value of myString.simpleExample is: " + myString.SimpleExample);
The result is
As you can see, the simpleExample property has undefined value just as any nonexistent property would.
Note |
C++ and Java programmers should be aware that JavaScript’s delete is not the same as in those languages. It is used only to remove properties from objects and elements from arrays. In the previous example, you cannot delete myString itself, though attempting to do so will fail silently. |
An equivalent but sometimes more convenient alternative to the dot operator is the array ([ ]) operator. It enables you to access the property given by the string passed within the brackets. For example:
var myString = new String("Hello world"); alert(myString["length"]); myString["simpleExample"] = true; alert(myString.simpleExample); delete myString["simpleExample"];
Some programmers prefer this method of accessing properties simply for stylistic reasons. However, we’ll see in later sections another reason to favor it: it can be more powerful than the dot-operator syntax because it lets you set and read properties with arbitrary names, for example, those containing spaces.
Properties that are functions are called methods. Like properties, they are typically accessed with the dot operator. The following example illustrates invoking the toUpperCase() method of the String object:
var myString = new String("am i speaking loudly? "); alert(myString.toUpperCase());
You could also use the array syntax,
var myString = new String("am i speaking loudly? "); alert(myString["toUpperCase"]());
but this convention is rarely used.
Setting instance methods is just like setting instance properties:
var myString = new String("Am I speaking loudly? "); myString.sayNo = function() { alert("Nope."); }; myString.sayNo();
Instance methods are most useful when the object is user-defined. The reason is that unless the object is user-defined, you usually don’t know its internal structure, and therefore can’t do as much as if you did.
A convenient way to iterate over the properties of an object is the for/in loop. This construct loops through the properties of an object one at a time, at each iteration assigning the name of a property to the loop variable. The result is that, in combination with the array syntax for accessing properties, you can do something with each property without having to know their names ahead of time. For example, you could print out the properties of an object and their values:
for (var prop in document) document.write('document["' + prop + '"] = ' + document[prop] + '<<br />> ');
The result in Mozilla and Internet Explorer is shown in Figure 6-1.
There are a few important subtleties of for/in loops. The first is that different browsers often enumerate a different set of members. Mozilla-based browsers enumerate both properties and methods, whereas Internet Explorer only enumerates properties. There are even some properties that many browsers never enumerate.
The primary issue to be aware of is that, typically, only instance properties of an object are enumerated. Given the following example,
var myString = new String("Niels is a poor foosball player"); myString.aboutFoosball = true; for (var prop in myString) document.write('myString["' + prop + '"] = ' + myString[prop] + '<<br />>');
you might expect more output than just the following:
Indeed, Mozilla shows more properties (see Figure 6-2), but they’re not exactly what you might expect either.
A final wrinkle to be aware of is that the order in which properties are enumerated is undefined. That is, there’s no guarantee as to the relative order in which they’ll be assigned to the loop variable, nor that the order will be consistent from one for/in loop to the next.
These facts, particularly that only instance properties are usually enumerated, mean that for/in loops are primarily useful with user-defined objects, where you’ve set instance properties and know there are none that are preexisting. These loops are often also helpful, particularly when debugging, and can also be used to satisfy your curiosity; many browsers implement undocumented properties that can be useful if one knows they exist.
Another convenient object-related operator is with:
with (object)
statement;
Using with lets you reference properties of an object without explicitly specifying the object itself. When you use an identifier within the statement or block associated with a with statement, the interpreter checks to see if object has a property of that name. If it does, the interpreter uses it. For example:
with (document.myForm) { if (username.value == "") alert("Must fill in username"); if (password.value == "") alert("Password cannot be blank. "); }
In this case, with lets you access document.myForm.username.value and document.myForm. password.value with a lot less typing. In fact, this is the primary use of with statements: to reduce the clutter in your scripts.
Note |
The advanced explanation of how this works is that object is temporarily placed at the head of the scope chain during the execution of the block. Any variables accessed in the statement are first attempted to be resolved in object, and only then are the enclosing scopes checked. |
All JavaScript data types can be categorized as either primitive or reference types.
These two types correspond to the primitive and composite types discussed in Chapter 3. Primitive types are the primitive data types: number, string, Boolean, undefined, and null. These types are primitive in the sense that they are restricted to a set of specific values. You can think of primitive data as stored directly in the variable itself. Reference types are objects, including Objects, Arrays, and Functions. Because these types can hold very large amounts of heterogeneous data, a variable containing a reference type does not contain its actual value. It contains a reference to a place in memory that contains the actual data.
This distinction will be transparent to you the majority of the time. But there are some situations when you need to pay particular attention to the implications of these types. The first is when you create two or more references to the same object. Consider the following example with primitive types:
var x = 10; var y = x; x = 2; alert("The value of y is: " + y);
This code behaves as you would expect. Because x has a primitive type (number), the value stored in it (10) is assigned to y on the second line. Changing the value of x has no effect on y because y received a copy of x’s value. The result is shown here:
Now consider similar code using a reference type:
var x = [10, 9, 8]; var y = x; x[0] = 2; alert("The value of y's first element is: " + y[0]);
The result might be surprising:
Because arrays are reference types, the second line copies the reference to x’s data into y. Now both x and y refer to the same data, so changing the value of this data using either variable is naturally visible to both x and y.
Another situation in which you to need to pay careful attention to reference types is when passing them as arguments to functions. Recall from Chapter 5 that arguments to functions are passed by value. Because reference types hold a reference to their actual data, function arguments receive a copy of the reference to the data, and can therefore modify the original data. This effect is shown by the following example, which passes two values, a primitive and a reference type, to a function that modifies their data:
// Declare a reference type (array) var refType = ["first ", " second", " third"]; // Declare a primitive type (number) var primType = 10; // Declare a function taking two arguments, which it will modify function modifyValues(ref, prim) { ref[0] = "changed"; // modify the first argument, an array prim = prim - 8; // modify the second, a number } // Invoke the function modifyValues(refType, primType); // Print the value of the reference type document.writeln("The value of refType is: ", refType+"<<br />>"); // Print the value of the primitive type document.writeln("The value of primType is: ", primType);
The result is shown in Figure 6-3. Notice how the value of the reference type changed but the value of the primitive type did not.
Another situation where you need to be careful with reference types (objects) is when comparing them. When you use the equality (==) comparison operator, the interpreter compares the value in the given variables. For primitive types, this means comparing the actual data:
var str1 = "abc"; var str2 = "abc"; alert(str1 == str2);
The result is as expected:
For reference types, variables hold a reference to the data, not the data itself. So using the equality operator compares references and not the objects to which they refer. In other words, the == operator checks not whether the two variables refer to equivalent objects, but whether the two variables refer to the exact same object. To illustrate:
var str1 = new String("abc"); var str2 = new String("abc"); alert(str1 == str2);
The result might be surprising:
Even though the objects to which str1 and str2 refer are equivalent, they aren’t the same object, so the result of the comparison is false.
This brings up the question: if you can’t check two objects for equality by using == on their references, how can you do it? There are two ways: by converting them to a primitive type or with a custom-built function.
Note |
The relational comparison operators (>>, <<, >>=, and <<=) work as you would expect for objects for which these operators make sense (e.g., Number, String, Date, and so on). The reason is that these operators automatically convert their operands to a primitive type, as we’ll see in the next section. The equality relational operator doesn’t do this because you might actually want to compare references. |
All JavaScript objects have the common properties and methods listed in Table 6-2. Most are useful only if you’re working with custom-built objects, and to tell the truth, many of these properties aren’t at all useful except to those performing advanced object-oriented acrobatics.
Property |
Description |
---|---|
prototype |
Reference to the object from which it inherits non-instance properties |
constructor |
Reference to the function object that served as this object's constructor |
toString() |
Converts the object into a string (object-dependent behavior) |
toLocaleString() |
Converts the object into a localized string (object-dependent behavior) |
valueOf() |
Converts the object into an appropriate primitive type, usually a number |
hasOwnProperty(prop) |
Returns true if the object has an instance property named prop, false otherwise |
isPrototypeOf(obj) |
Returns true if the object serves as the prototype of the object obj |
propertyIsEnumerable(prop) |
Returns true if the property given in the string prop will be enumerated in a for/in loop |
Two common methods you should know about are toString(), which converts the object to a primitive string, and valueOf(), which converts the object to the most appropriate primitive type, usually a number. These methods are automatically invoked when an object is used in a context that requires one or the other. For example:
alert(new Date());
Since alert() requires a string argument, the interpreter calls the Date object’s toString() method behind the scenes. The Date object knows how to turn itself into a string, so the result is
The valueOf() method is similar. Since it doesn’t make any sense to make a relational comparison of references, relational comparison operators require two primitive types to operate upon. So when you use one of these operators with objects, the objects are converted into their appropriate primitive forms:
var n1 = Number(1); var n2 = Number(2); alert(n2 >> n1);
The comparison causes the valueOf() methods of the two objects to be called so they may be compared.
The valueOf() method gives us a way to compare two objects for equality:
var n1 = Number(314); var n2 = Number(314); alert(n2.valueOf() == n1.valueOf());
This code happily gives us the expected output:
You typically won’t have to worry about manually converting values in this fashion. However, knowing that tools like valueOf() and toString() exist can be helpful should you find yourself with undesirable type-conversion or comparison behaviors, and especially if you’re creating your own user-defined objects.
Note |
The exact details of how valueOf() and toString() work in conjunction with various operators are beyond the scope of this book. Interested readers should consult the ECMA-262 specification. |