Article
JavaScript Object-Oriented Programming Part 2
In Part 1 of this series, we covered objects, object methods, and object categories. Now, let's move on.
Arguments
In every function, a private variable -- argument -- is automatically created, holding an array of the arguments passed to the function. For example:
function testArg(){
for(i=0;i<arguments.length;i++){
alert("Argument "+i+" is "+arguments[i]);
}
}
As demonstrated in the example above, we can access the set of arguments passed when calling a function with the arguments variable that exists in the function's scope. This example shows that we can access all of the arguments in a function without specifying them as parameters when we define the function. This can be particularly useful when we don't know exactly how many arguments we're going to pass.
Therefore, we can use:
testArg("PageResource","SitePoint","JavaScriptCity",
"WebSite Abstraction");
...to get an alert of some of my favorite Web development sites.
Complex Example
Now that we have a foundation in object-based programming in JavaScript, let's build an intricate object-based example, a library. We’ll just keep track of some basic information, such as the book titles, authors, pages, and price. To accomplish this, we're going to have a Person object (that represents each author), a Book object, and a Library object. First, let's create the Person() object constructor:
function Person(lastName, firstName){
this.lastName = lastName;
this.firstName = firstName;
}
And now, let's create some instances of our Person object:
var DnnyGdmn = new Person("Goodman","Danny");
var DvdFlngn = new Person("Flanagan","David");
var TmMyrs = new Person("Myers","Tom");
var AlxNkmvsky = new Person("Nakhimovsky","Alexander");
Next, let's create our Book object. Its properties will be:
- title
- pages
- price
- author(s)
Lastly, we can have multiple authors for the same book, so we need to be able to accept more than one Person object as an author. To do this, we'll create an array to hold each person that wrote the book.
function Book(title, pages, price){
this.title = title;
this.pages = pages;
this.price = price;
this.authors = new Array(arguments.length-3);
for(i=0;i<arguments.length-3;i++){
this.authors[i] = arguments[i+3];
}
}
The first part of that code should seem straightforward; however, the last part may not. So, let's examine it more closely:
this.authors = new Array(arguments.length-3);
This creates an author property for our Book object. The author property is itself an Array object. When we call our Book() constructor, the first three arguments are title, pages, and price, respectively, so those arguments that are specified after these are our authors. Therefore, if we pass five arguments, we know that two of those must be authors. So we can create an Array object with a length of arguments.length-3.
for(i=0;i<arguments.length-3;i++){
this.authors[i] = arguments[i+3];
}
This code loops through the arguments and assigns them to the Array object. Now, let's see how we can create instances of this Book object:
var JavaNut = new Book("Java Foundation Classes in a
Nutshell", 731, 29.95, DvdFlngn);
var JSTDR = new Book("Javascript: The Definitive Guide (3rd
Edition)", 776, 39.95, DvdFlngn);
var JSBible = new Book("Javascript Bible, 4th Edition",
1200, 49.99, DnnyGdmn);
var DHTMLTDR = new Book("Dynamic Html: The Definitive
Reference", 1073, 44.95, DnnyGdmn);
var JSObj = new Book("JavaScript Objects", 450, 39.99,
TmMyrs, AlxNkmvsky);
Note that we're passing instances of the Person object as the last arguments to create the authors property of the Book object. A key concept in OOP design (as in Relational Database design) is to avoid repetition within data. Therefore, we create one Person object for each distinct author. So, even though David Flanagan may write more than one book, we always refer to the same Person object. Also, if David ever decides to change his first name to "Bebop", we can easily change it for all records, by simply altering the one Person object that holds this information. In addition, note that instead of passing primitive data types, we could have passed objects. For instance, for the title, we could have passed a String object, and for the page number, we could have passed a Number object. However, here, they wouldn't serve much use, so we used a primitive data type - this fits our needs just perfectly.
Now, let's move on to perhaps the most difficult object constructor, the Library() constructor. I'm going to break this one up in to parts:
function Library(){
this.books = new Array(arguments.length);
for(i=0;i<arguments.length;i++){
this.books[i] = arguments[i];
}
The first thing you may notice about this function is that it has no parameters. This is because it only accepts Book objects, though we have no idea how many. It creates a property of the Library object, books, which stores an Array of Book objects. Let's say we wanted to access a book's first-listed author. We could use:
this.books[bookIndex].authors[0]
We first access the Library’s book property, which is an Array object. Then, we access a specific Book object. From that Book object, we access its authors property, which is an array. Lastly, we access a specific Person object. And from there, we could go on to access that Person object’s firstName or lastName property. Note that bookIndex is the index of the book we want to access.
Now, that's the only property that our Library object will contain. The rest will be methods:
this.totalPrice = function(){
var totalCost = 0;
for(i=0;i<this.books.length;i++){
totalCost += this.books[i].price;
}
return totalCost;
}
This method loops through our books property, which is an Array object, takes the price of each Book object and adds it up, and finally returns the value.
this.averagePrice = new Function("return this.totalPrice
()/this.books.length");
This method takes the total price of all of our books and divides it by the number of books we have, to find out the average price of our books. So, once we create a Library object, how do we add more books to it? We're going to have to create another function:
this.addBook = new Function("book", "this.books.push(book)");
This uses the Array's built-in method, push(). The push() method adds the value or object passed as an argument to its Array object, while making sure to change the Array's length property. Lastly, we'll create a method to display the names of the authors in our library. This method is rather long, so I'll split it up:
this.getAuthors = function(){
var toSay = "Your favorite authors are:\n";
This creates a method we'll use to retrieve the list of authors. The toSay variable will hold the string of what this method returns.
for(i=0;i<this.books.length;i++){
for(j=0; j<this.books[i].authors.length;
j++){
var authName =
this.books[i].authors[j].firstName + " " +
this.books[i].authors[j].lastName;
This portion of the code loops through all the books, and then loops through all the authors of that book, placing their names in the authName variable.
if(toSay.indexOf(authName)!=-1) continue;
toSay+="\n\t"+authName;
If this author is already in the toSay variable, we don't want to add him again, so we continue to loop through the authors of this book. However, if the author is not yet listed, we can go ahead and add him or her to the toSay variable.
}
}
return toSay;
}
}
That closes the two for loops we had open, and returns the toSay variable. It also closes out the method we've been defining, getAuthors(), as well as closing out the Library() constructor. Now, let's put all the code together, and create a new Library object:
// define our Person() constructor
function Person(lastName, firstName){
this.lastName = lastName;
this.firstName = firstName;
}
// define our Book() constructor
function Book(title, pages, price){
this.title = title;
this.pages = pages;
this.price = price;
this.authors = new Array(arguments.length-3);
for(i=0;i<arguments.length-3;i++){
this.authors[i] = arguments[i+3];
}
}
//define our Library() constructor
function Library(){
this.books = new Array(arguments.length);
for(i=0;i<arguments.length;i++){
this.books[i] = arguments[i];
}
this.totalPrice = function(){
var totalCost = new Number(0);
for(i=0;i<this.books.length;i++){
totalCost += this.books[i].price;
}
return totalCost;
}
this.averagePrice = new Function("return
this.totalPrice()/this.books.length");
this.addBook = new
Function("book","this.books.push(book)");
this.getAuthors = function(){
var toSay = "Your favorite authors are:\n";
for i=0;i<this.books.length;i++){
for(j=0;j<this.books[i].authors.length;j++){
var authName =
this.books[i].authors[j].firstName + " " +
this.books[i].authors[j].lastName;
if(toSay.indexOf(authName)!=-
1)continue;
toSay+="\n\t"+authName;
}
}
return toSay;
}
}
// create some Person objects
DnnyGdmn = new Person("Goodman","Danny");
DvdFlngn = new Person("Flanagan","David");
TmMyrs = new Person("Myers","Tom");
AlxNkmvsky = new Person("Nakhimovsky","Alexander");
// create some Book objects
JavaNut = new Book("Java Foundation Classes in a
Nutshell",731,29.95,DvdFlngn);
JSTDR = new Book("Javascript: The Definitive Guide (3rd
Edition)",776,39.95,DvdFlngn);
JSBible = new Book("Javascript Bible, 4th
Edition",1200,49.99,DnnyGdmn);
DHTMLTDR = new Book("Dynamic Html: The Definitive
Reference",1073,44.95,DnnyGdmn);
JSObj = new Book("JavaScript
Objects",450,39.99,TmMyrs,AlxNkmvsky);
// create a Library object
myLib = new Library(JavaNut,JSTDR,JSBible,DHTMLTDR);
Oops, we left out the JavaScript Objects book. We'd better add it:
myLib.addBook(JSObj);
Now, we can get the information, such as how much our library of books cost, the average price of a book, and the names of the authors that have written the various books we own. And that's it! We've completed a complicated OOP example with JavaScript. You might want to go back over any code that you don't understand, or feel free to post a question in the Client Side Scripting forum at SitePointForums.com.
Ryan has been involved with client-side scripting since 1999 when he started high school. He recently graduated from Carnegie Mellon University in May 2007, where he was a teaching assistant for 4 semesters and co-taught a stuco course on