Published on 07/01/12 at 08:23:43 EST by GentleGiant
The idea of object-oriented programming is not a new one. It actually dates back over 30 years, and has gone through several phases of popularity in that time. Currently, object-oriented programming is considered by many to be an established concept that should be part of all modern programming languages. There are several different conflicting definitions of object-oriented programming. Fortunately, there are some key concepts that are shared by (almost) all versions of objected-oriented programming.
At its most basic level object-oriented programming is a style of programming in which related concepts are grouped together. If you have five data elements and three functions that manipulate those elements, then you group those elements and functions together into a generic container known as an object. This is the common ground shared by (almost) all object-oriented programming languages. Differences arise in the details of how such containers are organized, and in how their contents can be accessed and modified.
An analogy can be made between home ownership and object-oriented programming. Everyone's house has a kitchen, some bedrooms and bathrooms, stairs, flooring, and so forth. Some homes have spiral staircases, Art Deco ironwork, and a gazebo in the back yard. Others have a completely utilitarian layout based on a linear architecture with not a rounded corner in sight. When talking about your home, you describe both the basic aspects ("yes, of course we have a basement") and also the embellishments ("the basement has a painfully hard cement floor"). When talking about what an object means in JavaScript, it's necessary to also talk of two levels. The basic aspects of the way JavaScript handles objects is known as its object model. The embellishments constitute the extensive set of features of the predefined objects in JavaScript, as well as those aspects of the language that can be used to create and use new, user-defined objects.
In this chapter you learn the particulars of the JavaScript object model, and are introduced to the various built-in objects and their uses. After completing this chapter you will be able to do the following:
Use the Date, String, and Math objects. These objects are built in to JavaScript, and provide many commonly used functions for date and string manipulation, as well as a large collection of mathematical operations. Create new objects with specific properties and methods. User-defined objects are an excellent way of grouping related data items and functions together, and work well with JavaScript's object model. Build and use JavaScript arrays. JavaScript arrays can be accessed by using a numerical index or an element name; this dual capability simplifies many programming tasks. Understand the hierarchy of objects on a Web page. Almost every HTML element is associated with a JavaScript object. These objects are arranged in a logical hierarchy, much like a directory tree. Associate HTML tags with HTML objects. To access HTML objects from JavaScript it is necessary to explore JavaScript's rules for HTML elements and their attributes. Objects, Properties, and Methods in JavaScript Before we can delve into object-oriented programming in JavaScript, it is first necessary to review some of the basic concepts of object-oriented programming itself. You have already had a brief introduction in the "Functions and Objects" section of Chapter 2, "JavaScript: The Language." This section takes you further, and explains several critical and often misunderstood ideas.
Object-Oriented Programming Concepts We already know that an object is basically a container for related items. Rather than carry around money and credit cards in many different pockets and folders, many people choose a more unified method: they keep their money in a wallet. Perhaps they even keep their change in a change purse. The wallet is a container for related items. This is not to say that all such items must be in that wallet; this is often a near-impossible goal for even the most organized individuals. As a flexible principle, however, it is of enormous utility.
Objects operate the same way. Objects collect related data items in a single place and make it simpler, or at least more logical, to access those items. As we have already seen, JavaScript refers to the items collected within an object as its properties. You may also recall that JavaScript objects not only store data, they also store functions. It is useful to keep functions that manipulate data items in a specific way with those data items themselves. These functions are known as the methods of an object.
The JavaScript Date object is a perfect example of the benefits of this kind of organization. As the name implies, a JavaScript Date object is used to store a date, and also a time. The Date object also has a very particular set of methods that are useful in converting string representations of dates in Date objects. While these functions are vitally important when manipulating strings such as "Nov 23, 1990," they do not really have sweeping application elsewhere. In a word, they are date-specific. It makes good sense to keep these methods with Date objects, rather than making them generally available functions.
In addition to the concepts of object, property, and method there is a fourth, somewhat more subtle, concept that is also of great importance: the instance. The relationship between an object and an instance of an object is the same as the relationship between a data type and a variable of that data type. In the typeless language such as JavaScript, this distinction is blurred but is still present. Another way to think of this distinction is to think of an object as a set of shelves, some of which may be occupied while others are not. You convert that object into an instance when you completely fill in all the empty shelves.
While the object Date is an abstract thing that does refer to any specific date, an instance of the Date object must refer to some specific date. Its empty slots, which specify the actual day, month, year, and so forth, have all been assigned specific values.
Defining Your Own Objects: The new Statement Now that we have presented the basic object foundation upon which JavaScript rests, it is time to consider how these concepts are implemented. How does one create objects and instances in JavaScript? In fact, you already know part of the answer to this question, as objects are created by defining a very special sort of function.
Let's pursue the home ownership analogy even further and define a house object. The fundamental properties of our house object will be as follows:
Number of rooms Architectural style Year built Has a garage? To define an object to hold this information, we write the function shown in listing 4.1. Note that this function makes use of the extremely important keyword this, which always refers to the current object. In this case it refers to the current object we are creating.
Listing 4.1 Defining a Function to Create a house Objectfunction house( rms, stl, yr, garp ) { // define a house object this.rooms = rms; // number of rooms (integer) this.style = stl; // style, e.g. Colonial, Tudor, Ranch (string) this.yearbuilt = yr; // year built, integer this.hasgarage = garp; // has a garage? (boolean) }
There are several things to notice about this object definition. First of all, the name of the function is the name of the object: house. Second, this function does not return anything. When functions were first introduced in chapter 2, it might have seemed mysterious how a function could actually do useful work without a return statement, since everything inside a function is local. Using a function to create an object works by modifying this, so that it need not return anything. You can also have the function return(this). Using this explicit return statement has the same effect as the code shown in listing 4.1.
This example shows how a house object is defined. It does not create a specific house instance. The house object has four slots to hold the four properties rooms, style, yearbuilt, and hasgarage. A specific house instance will fill those slots with actual values. Instances are created using the new statement combined with a function call. The keyword new is required, since it tells JavaScript that we are creating an instance rather than just calling a function. We could create an instance of house, named myhouse, as follows:
var myhouse = new house( 10, "Colonial", 1989, true );
Note that the instance myhouse is treated just like any other variable. It must be declared using var. Now that myhouse has been created we can refer to its properties using the dot operator (.). myhouse.rooms has the value 10, myhouse.style is the string "Colonial," myhouse.yearbuilt is 1989, and myhouse.hasgarage is the Boolean value true. The fact that rooms and yearbuilt are integers, style is a string, and hasgarage is a Boolean is only implicit, of course. There is nothing stopping us from creating a house instance in which the hasgarage property has the string value "yes" rather than a Boolean value. Care must be taken to avoid this kind of type confusion.
Object properties are typeless, just like all other variables in JavaScript. The new operator does not protect you against inadvertently assigning an inappropriate value to a property. Objects as Arrays Many programming languages support array data types. An array is an indexed collection of items all of which have the same underlying type. In C or Java, for example, we can say int iarr[10]; which defines a collection of 10 integers. These integers are referred to as iarr[0] through iarr[9]. These two languages use zero-based indexing, which means that the first element of the array is at location 0 and the last element of the array is at one less than the length of the array-9 in this case. Other languages have one-based indexing, in which the elements range from 1 up to the length of the array. This might seem more intuitive, but zero-based indexing is actually the more common form.
JavaScript also has arrays that use zero-based indexing. In JavaScript, however, arrays and objects are really two views on the same concept. Every object is an array of its property values, and every array is also an object. Our myhouse instance, for example, is an array with the following four elements:
There might not seem to be a lot of advantage to referring to objects in this more numeric and less informative manner. You have to remember which index corresponds to which property. However, this alternate form of access makes it possible to access the properties sequentially, rather than by name, which is sometimes very useful. If we know that house objects always have four members then we can write the function shown in listing 4.2 to display the property values.
Listing 4.2 A Function That Displays the Properties of a house function showhouse( somehouse ) { // display properties of a house instance for( var iter = 0; iter < 4; iter++) { // four properties exactly document.write("<BR>Property " + iter + " is " + somehouse[iter]); } document.write("<BR>"); }
If we call this function as showhouse( myhouse ) the four properties of the myhouse instance are displayed. This function must be called with an instance, not an object. It would be an error to try showhouse( house ). Since there are several alternative ways of writing it, we will revisit this function when we have learned more about methods and the for...in statement.
One deficiency of the showhouse function should strike you immediately. It relies on the implicit knowledge that every house instance has exactly four properties. If we were to augment the definition of a house object by adding a property known as taxrate (a floating-point number describing the current real estate taxation rate on the house), then the showhouse function would need to be modified to increase the loop count in the for statement from 4 to 5. If we neglect to do so then the showhouse function would only print the first four properties, and would never print the taxrate.
An even more disastrous error would occur if we defined the house object to have only three properties, but forgot to drop the loop count to 3; then the reference to somehouse[3] would refer to a nonexistent array member. This type of error is known as an out of bounds error, since it refers to an array element that was not within the boundaries of the array. There is a very simple way to avoid this problem and write the showhouse function in a more general manner.
Define all objects with a length property, which gives the number of properties in the object. Make the length property the first property. Using the preceding tip, we can rewrite the definition of the house object to include a length property as the first property, and then generalize the showhouse function to be completely independent of any prior knowledge of the house object. This code for the new house object and showhouse function is shown in listing 4.3.
Listing 4.3 A Better house Object That Knows Its Own Length /* This function creates a house instance whose first property, at array index 0, contains the number of properties in the house instance. */
function house ( rms, stl, yr, garp ) { this.length = 5; // four informative properties, and length this.rooms = rms; // rooms this.style = stl; // architecture style this.yearbuilt = yr; // year constructed this.hasgarge = garp; // does it have a garage? }
/* This function displays a house instance using its length property to determine how many other properties to display */
function showhouse( somehouse ) { // display properties of a house instance var nprops = somehouse.length; // number of properties for( var iter = 1; iter < nprops; iter++) { // iterate over all properties except length document.write("<BR>Property " + iter + " is " + somehouse[iter]); } document.write("<BR>"); }
This house object function takes four parameters, as before. It sets its length property to this number plus 1, since there are four meaningful properties (rooms, style, yearbuilt, and hasgarage) and the length property itself. Each of the meaningful properties have moved up 1, so that if we say myhouse = new house( 10, "Colonial", 1989, true) the array representation of myhouse becomes
The showhouse function starts by looking at the length property and uses that to set the termination condition for the for loop. The constant 4 of listing 4.2 has been replaced by the variable nprops which holds the length of the myhouse array. This version of showhouse only prints the properties of interest; it does not print the length property. This is why the for loop begins at 1 rather than at 0. The property myhouse[0] is the length property.
This use of the length property is a typical example of the true nature of object-oriented programming. One of the fundamental ideas in object-oriented programming is the idea of encapsulation, which is a long-winded way of saying keeping related things in the same place. In the previous definitions of house and showhouse (see listings 4.1 and 4.2), the length of the house object was present in two places. It was implicitly present in the definition of house itself, and it was also present explicitly, as the upper limit in the for loop. The doctrine of encapsulation says that this is bad. The length of an object should only be stored in one place-in the object itself. By the same token it might be argued that the showhouse function should really be part of the house object, too. The "Method Functions" section later in this chapter describes how to do this.
Despite the power of this technique, it might still seem less than obvious to refer to properties by index rather than by property name. JavaScript provides a third technique, which is a hybrid of the dot style (.) and the array style ([]). Object properties may be referred to not only as indexed array elements but also as named array elements. This type of array is known as an associative array. The set of properties of the myhouse instance could also be listed as
JavaScript arrays can be accessed by integer index or by property names. Property names are case-sensitive. Integer indices are limited by the length of the array. If you refer to non-existent array elements, by name or by index, it either generates a JavaScript error or gives you an invalid value. Using Variable Length Arrays and Extended Instances There is one final point to be made about the difference between house object and its various instances. Suppose we create another instance of house, named yourhouse, using the following call to new:
yourhouse = new house( 26, "Tudor", 1922, true );
myhouse and yourhouse are both instances of the house object. Both result from filling in the four slots in the house template with four specific pieces of information that define myhouse and yourhouse (as well as the fifth, hidden piece of information, the length). It is possible to dynamically extend an instance by simply tacking on a new property. If you feel the need to also record the fact that your house has two tool sheds and a gazebo you can write
yourhouse.sheds = 2; yourhouse.hasgazebo = true; These two statements add two new properties to the end of the yourhouse array. The sheds (integer) property is yourhouse[5] and the hasgazebo (Boolean) property is yourhouse[6]. Dynamic extensions only apply to specific instances. The myhouse instance is not affected, nor is the house object changed in any way. If we execute showhouse( myhouse ) it prints out exactly the same as it did before. If we create a third house named pizza
pizza = new house( 3, "Restaurant", 1993, false ); it will not have either a sheds property or a hasgazebo property. Figure 4.1 illustrates the relationship between the house object and its various instances.
Instances inherit their structure from the underlying object, but can also be extended.
Dynamic extensions are completely local to a particular instance. The underlying object and all other instances-past, present, and future-are not affected. There are some situations in which dynamic extensions are absolutely essential, and dramatically simplify programming. For the most part, however, dynamic extensions should be used with great care, as they can be the source of numerous errors. In fact, we have already made one such error, which shows itself if we attempt to execute the function showhouse( yourhouse ). Since the length element of the yourhouse instance has not been modified, it still has the value 5, so that only array elements 1 through 4 (properties "name" through "hasgarage") are displayed. The two new properties will not be displayed. When we added sheds and hasgazebo, we should have also said
yourhouse.length += 2;
to account for the two new properties in this instance. This is precisely the type of error that is easy to make. In general, it would be much better for the house object to always have sheds and hasgazebo properties, which are seldom used, than to randomly glue them on.