JavaScript Object-Oriented Programming Part 2

    技术2022-05-12  16

    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:

    titlepagespriceauthor(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]; }

    src="http://ad.doubleclick.net/adi/N339.sitepoint.com/B3141818;sz=300x250;ord=cf7d078335?" marginwidth="0" marginheight="0" hspace="0" vspace="0" bordercolor="#000000" scrolling="no" width="300" frameborder="0" height="250"> &amp;lt;SCRIPT language='JavaScript1.1' SRC="http://ad.doubleclick.net/adj/N339.sitepoint.com/B3141818;abr=!ie;sz=300x250;ord=cf7d078335?"&amp;gt; &amp;lt;/SCRIPT&amp;gt;

    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.

    ==============================

    Prototype

    Every object constructor has a special property, prototype. This property allows you to add properties/methods to all objects created from that object constructor. Sound confusing? It's not. Let's look at some examples:

    function Square(){ } var squareObj = new Square(); Square.prototype.side = 5; var squareObj2 = new Square(); alert(squareObj.side); // displays 5 alert(squareObj2.side); // displays 5

    What this does is add a side property, with an initial value of 5, to all Square objects, whether they've been created, or have yet to be created. The prototype object (it is, in fact, an object) loads before the object constructor does anything. So, this code:

    function Square(){  this.side=5; } var squareObj = new Square(); Square.prototype.side = 4; var squareObj2 = new Square(); alert(squareObj.side); // displays 5 alert(squareObj2.side); // displays 5

    returns 5, because everything in the prototype object loads first (before the Square() object constructor even runs), so the properties and methods defined in the constructor will override it. So, with the prototype property, you can't override any properties or methods defined in an object's constructor (the function that creates the object). Using the String's prototype property, we can add new methods to String objects. Consider this example:

    function consonantize(){  var consonants ="";  for(i=0;i<this.length;i++){    var l = this.charAt(i);    if(l!="a" && l!="A" && l!="e" && l!="E" &&    l!="i" && l!="I" && l!="o" && l!="O" && l!="u" && l!="U" && l!=" "){      consonants+=l;    }  }  return consonants; }

    The above function goes through a string and removes all vowels and spaces, returning only consonants. Now, we can use it on any String object, or any String primitive datum:

    String.prototype.consonantize = consonantize; var dg = "Danny Goodman"; var df = new String("David Flanagan"); alert(dg.consonantize()); alert(df.consonantize());

    Neat, huh? Note how the new method, just like other String methods, can be used by a String object or by a String primitive data type. Therefore, by using an object constructor's prototype method, we can add properties and methods to both native objects and user-defined objects.

    ============================

    Constructor

    Every instance of an object has a constructor property. It returns the Function object that created that instance of the object. For example:

    function myConstructor(){ } var str = new String("Some String"); var obj = new Object(); var myObj = new myConstructor(); alert(str.constructor); // the native String() constructor alert(String) // the native String() constructor alert(obj.constructor); // the native Object() constructor alert(Object) // the native Object() constructor alert(myObj.constructor); // the user-defined myConstructor() constructor alert(myConstructor); // the user-defined myConstructor() constructor

    I recommend that you run this example to see what it returns. Notice how each alert returns the Function object that created that instance of the object. Also, notice that JavaScript's native objects return "[native code]". When you retrieve the typeof for a constructor property, you'll find that it's the same as the Function object that created it, "function":

    alert(typeof str.constructor); // "function" alert(typeof String) // "function" alert(typeof obj.constructor); // "function" alert(typeof Object) // "function" alert(typeof myObj.constructor); // "function" alert(typeof myConstructor); // "function"

    All of the above return "function". Because a constructor property returns a reference to the Function object that created it, the constructor is in fact a constructor method:

    function myConstructor(){  var x = "y";  this.x = "x";  return x; } var myObj = new myConstructor(); alert(myObj.constructor); // the myConstructor() function object alert(myObj.constructor()); // "y"

    Note that in this example, we return the local variable, x, rather than the object's property, this.x. So, if every object has a constructor method, and every method is really a Function object, what's a Function object's constructor?

    alert(myConstructor.constructor); alert(myObj.constructor.constructor); alert(myConstructor.constructor.constructor); alert(myObj.constructor.constructor.constructor);

    src="http://ad.doubleclick.net/adi/N339.sitepoint.com/B3141818;sz=300x250;ord=077a4a8990?" marginwidth="0" marginheight="0" hspace="0" vspace="0" bordercolor="#000000" scrolling="no" width="300" frameborder="0" height="250"> &amp;lt;SCRIPT language='JavaScript1.1' SRC="http://ad.doubleclick.net/adj/N339.sitepoint.com/B3141818;abr=!ie;sz=300x250;ord=077a4a8990?"&amp;gt; &amp;lt;/SCRIPT&amp;gt;

    All of those return the native Function() object constructor. Although that's trivial, I personally thought it rather interesting- and thought you might too, and it brings me to another point. Constructors are both "types of objects" as well as objects themselves (more specifically, Function objects). Thus, Date is both an object (a Function object) and a "type of object", from which you can create Date objects, or instances of the Date object. This is true for all native objects and user-defined objects.

    The practical value of all this is that, via an object's constructor method, we can figure out what type of object it is. We can see whether it's a String object, created from the native String constructor function; whether it's an Object object, created from the native Object constructor function; or whether it's one of our user-defined objects, created from a user-defined constructor function.

    Besides being a method of an object, constructor() is also a method of a primitive data type. So what does it return? After all, no real constructor function was run to create primitive data types:

    var primitiveString1 = "This is a primitive string"; var primitiveString2 = String("This is a primitive string"); var stringObject = new String("This is a String object"); primitiveString1.prop = "This is a property";   primitiveString2.prop = "This is a property"; stringObject.prop = "This is a property"; alert(primitiveString1.prop) // "undefined" alert(primitiveString2.prop) // "undefined" alert(stringObject.prop) // "This is a property" alert(typeof primitiveString1); // "string" alert(typeof primitiveString2); // "string" alert(typeof stringObject) // "object" alert(primitiveString1.constructor); // "function String(){   [native code] }" alert(primitiveString2.constructor); // "function String(){   [native code] }" alert(stringObject.constructor); // "function String(){   [native code] }"

    As we can see, both a String primitive data type and a String object have the same constructor(), the native String() constructor. Note that constructor() is the only property/method that a primitive data type holds, so these data types have access to the properties/methods defined in the native object constructor function. For example, a primitive String data type (as well as a String object) has access to the many properties/methods defined in the native String() constructor, including:

    lengthanchor()big()bold()charAt()charCodeAt()concat()indexOf()lastIndexOf()sub()substr()substring()

    However, a String object may also contain properties/methods that are particular to that object. For example:

    var myStringObj = new String("This is a String object"); myStringObj.prop = "This is a property of the object I created"; alert(myStringObj.prop) // "This is a property of the object I created"

    As Alex Vincent notes, sometimes you'll want to turn a primitive data type into an object. For example, let's say we have a function like this:

    function myFunc(param){  param.property = "I want to add this property";  alert(param.property); // "undefined" }

    If we decide to use this function and pass it a primitive data type, we can't also add properties to it, because it's not an object. And anyway, passing an object is rather cumbersome:

    myFunc(new String("This is a String object")); myFunc(new Number(5));

    One way to overcome this, as Alex points out, is as follows:

    function myFunc(param){  param = new param.constructor(param);  param.property = "I want to add this property";  alert(param.property); // returns "I want to add this property" }

    That new line looks confusing, but let's take a step back. Imagine that we wanted to change a primitive Number into a new Number object. We could use:

    var myNum = 5; myNum = new Number(5);

    Now let's take that a step further:

    var myNum = 5; myNum = new myNum.constructor(5);

    You must remember that myNum.constructor() is the same as Number(). Then, instead of using 5, we can use myNum, as that, too, is 5:

    var myNum = 5; myNum = new myNum.constructor(myNum);

    And the same works for a String primitive data type - as it does for all primitive data types. Therefore, when we pass any primitive data type as an argument to our function, we automatically convert it to an object so that we can add properties/methods to it.

    ============================

    Prototype Revisited

    Let's go back and revisit the Function object's prototype property. In Java, a popular, well-known feature is to extend a class; however, in JavaScript, most people are unaware that you can do this - but you can! For instance, let's say we have a Car object. A Corvette and an Ares are two different types of cars, but they are both still cars. In this way, they have similar properties/methods and extend upon the Car object.

    Let's create the three objects we're going to use - Car, Corvette, and Ares. Then, we'll discuss the ways for the latter two to inherit the properties/methods of the Car object.

    function Car(color){  this.wheels = 4;  this.doors = 4;  this.color = color;  this.speed = 0;  this.accelerate = function(){    this.speed+=20;  }  this.brake = function(){    this.speed-=20;  } } function Corvette(color){  // all of Car properties/methods  this.doors = 2;  this.color = color;  this.accelerate = function(){    this.speed+=40;  } } function Ares(color){  // all of Car properties/methods  this.doors = 2;  this.color = color;  this.accelerate = function(){    this.speed+=10;  }  this.brake = function(){    this.speed-=10;  } } var myCar = new Car("white"); var myCorvette = new Corvette("black"); var myAres = new Ares("red");

    src="http://ad.doubleclick.net/adi/N339.sitepoint.com/B3096204;sz=336x280;ord=821a1fab29?" marginwidth="0" marginheight="0" hspace="0" vspace="0" bordercolor="#000000" scrolling="no" width="336" frameborder="0" height="280"> &amp;lt;SCRIPT language='JavaScript1.1' SRC="http://ad.doubleclick.net/adj/N339.sitepoint.com/B3096204;abr=!ie;sz=336x280;ord=821a1fab29?"&amp;gt; &amp;lt;/SCRIPT&amp;gt; &amp;lt;NOSCRIPT&amp;gt; &amp;lt;a href="http://ads.aws.sitepoint.com/phpadsnew/www/delivery/ck.php?oaparams=2__bannerid=738__zoneid=80__cb=821a1fab29__maxdest=http://ad.doubleclick.net/jump/N339.sitepoint.com/B3096204;abr=!ie4;abr=!ie5;sz=336x280;ord=821a1fab29?" target="_blank"&amp;gt; &amp;lt;IMG SRC="http://ad.doubleclick.net/ad/N339.sitepoint.com/B3096204;abr=!ie4;abr=!ie5;sz=336x280;ord=821a1fab29?" BORDER=0 WIDTH=336 HEIGHT=280 ALT="Adobe ColdFusion 8 Ad"&amp;gt;&amp;lt;/A&amp;gt; &amp;lt;/NOSCRIPT&amp;gt;

    Because a Corvette is an especially fast car, we've upped its acceleration speed from a normal car, and because a Dodge Ares is a rickety, old car, we've made it so the brakes don't work as well, and it doesn't accelerate as fast (no offense to Dodge Ares owners). Now, we could use the Corvette() and Ares() prototype property and add to each the properties/methods from the Car object that we want them to inherit. However, this could be a confusing and tedious task, especially if there are many properties/methods. To overcome this, we need to examine the prototype property again.

    The prototype property is an object with no initial properties/methods. When we add properties/methods to this object, we automatically add them to all instances of the object. However, instead of adding properties/methods to the prototype object, we could replace the prototype object with an object that already has the properties/methods we want. For example, instead of using:

    Corvette.prototype.wheels = 4; Corvette.prototype.speed = 0; Corvette.prototype.brake = function(){  this.speed-=20; }

    we can more easily use:

    Corvette.prototype = new Car();

    We can do the same for the Ares object:

    Ares.prototype = new Car();

    Both the Corvette and Ares objects now have all the Car's properties/methods, which can then be overridden by the properties/methods defined in each object constructor. For example, in both the Corvette and Ares objects, the door property is overridden to 2. Altogether, our now code looks like:

    function Car(color){  this.wheels = 4;  this.doors = 4;  this.color = color;  this.speed = 0;  this.accelerate = function(){    this.speed+=20;  }  this.brake = function(){    this.speed-=20;  } } function Corvette(color){  this.doors = 2;  this.color = color;  this.accelerate = function(){    this.speed+=40;  } } Corvette.prototype = new Car(); function Ares(color){  this.doors = 2;  this.color = color;  this.accelerate = function(){    this.speed+=10;  }  this.brake = function(){    this.speed-=10;  } } Ares.prototype = new Car(); var myCar = new Car("white"); var myCorvette = new Corvette("black"); var myAres = new Ares("red");

    Now, from the Corvette and Ares objects, we can retrieve the appropriate properties and run the accelerate() and brake() methods that correspond to those objects. In this way, in JavaScript, object inheritance is not hard to accomplish.

    Wrap-up

    Through this tutorial, I hope you've learned a general understanding of how JavaScript operates. In addition, I hope you've gained a basic knowledge of OOP and an understanding of the power of JavaScript as an object-based language. I suggest that you post any questions you might have in the SitePoint Forums; however, if you can't seem to find an answer to your JavaScript object question, I'd be more than happy to give it a shot if you email me at arielladog@yahoo.com

    There have been many people who have helped me write this tutorial. In particular, though, I'd like to thank Alex Vincent, Jason Davis, and Jared for helping me to understand the finer points of JavaScript's object abilities.    

     

     

          


    最新回复(0)