1. Trang chủ
  2. » Công Nghệ Thông Tin

Practical prototype and scipt.aculo.us part 26 docx

6 87 0

Đang tải... (xem toàn văn)

THÔNG TIN TÀI LIỆU

Nội dung

Brevity Brevity is a by-product of cleanliness. Functions can be named more concisely when they don’t have to worry about conflict. Which would you rather type? stringReplace("bar", "r", "z"); "bar".replace("r", "z"); The second option is seven characters shorter than the first. Over time, this savings will add up—your code will be clearer and your wrists will thank you. Remedial OOP: Namespacing Before we get into “true” OOP, let’s look at how objects can be used to isolate our code and make it more resilient in a shared scripting environment. Because scripts from different authors often coexist on the same page, it’s a bad idea to define too many functions in the global scope. The more there are, the more likely one of them will overwrite someone else’s function with the same name. Instead, what if you were to define one object in the global scope, and then attach all your functions to that object? You’d balk at defining a method named run, but BreakfastLog.run is much less risky. The first line of defense in the cleanliness wars is namespacing your code. Let’s look at some of our code from a previous chapter: function submitBreakfastLogEntry(event) { if (event.target.id === 'cancel') cancelBreakfastLogEntry(event); // et cetera } function cancelBreakfastLogEntry(event) { event.stop(); event.target.up('form').clear(); // et cetera } Since they’re related, these functions ought to be placed into a common namespace. CHAPTER 7 ■ ADVANCED JAVASCRIPT: FUNCTIONAL PROGRAMMING AND CLASS-BASED OOP 141 var BreakfastLog = { submitEntry: function(event) { this.form = event.target.up('form'); if (event.target.id == "cancel") this.cancelEntry(event); // et cetera }, cancelEntry: function(event) { event.stop(); this.form.clear(); // et cetera } }; Here, we’re using the familiar object literal in a new way: as a container for like- minded methods and variables. Remember that {} is a shortcut for new Object, and that the new keyword triggers a new scope. Thus, inside any particular method, the keyword this refers to the BreakfastLog object. Code running outside the BreakfastLog object must explicitly call BreakfastLog.cancelEntry, but code running inside has the privilege of referring to it as this.cancelEntry. Similarly, the object itself can be used to pass information between methods. Notice how cancelEntry can read this.form, a property set by submitEntry (and that would be referred to as BreakfastLog.form from another scope). I’ve been casually referring to this as “namespacing,” even though it’s not a true namespacing solution like that of Perl or Ruby. There’s still a risk of naming collisions— after all, another library you use could create a BreakfastLog object—but it’s much less likely when there are fewer items in the global namespace. We can’t eliminate the risk altogether, but we can minimize it. Advanced OOP: Using Classes In Chapter 1, you learned about JavaScript’s prototypal inheritance model. Each function has a property called prototype that contains the “template” for making new objects of that type. Since every function has this property, every function can be instantiated. (Although this also means that “instantiate” isn’t the correct term; prototypal inheritance does not distinguish between classes and instances.) Prototypal inheritance isn’t better or worse than class-based inheritance, but you may not feel that way if you’re not used to it. Most of what you’re used to just isn’t there: no class definitions, no explicit way to define inheritance, no information-hiding. These things are hard not to miss. CHAPTER 7 ■ ADVANCED JAVASCRIPT: FUNCTIONAL PROGRAMMING AND CLASS-BASED OOP142 Luckily, nearly anything can be done in JavaScript if you’re willing to hack at it long enough. Prototype features a Class object that can mimic most of the features of class- based inheritance. It’s not meant to change the way JavaScript works; it’s just a different approach to OOP—an extra tool to have on your belt. Example Here’s a glimpse at the project we’ll be working on in the second part of the book. It’s a site about football (American football to the rest of the world). If you know nothing about the game, that’s OK—the bits you need to know will be learned along the way. A football team consists of an offense and a defense, each of which is divided into many positions and roles. But every player has the ability to score points. (For some play- ers, it would only happen by accident, but it’s still possible.) So each player has certain characteristics (things he is) and certain capabilities (things he can do). Those characteristics and capabilities vary based on his position, but there are some that are common to all players. This sounds like the perfect use case for class-based OOP. Creating the Base Class First, we’ll define a Player class—one that describes a generic football player: var Player = Class.create({ initialize: function(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; this.points = 0; }, scorePoints: function(points) { this.points += points; }, toString: function() { return this.firstName + ' ' + this.lastName; } }); Let’s consider the bold sections of code in order. Class.create is the Prototype method for building a new class. It accepts one argu- ment: an object literal that contains the properties the class should have. Most of the time, these will be methods, but they can be whatever you like. CHAPTER 7 ■ ADVANCED JAVASCRIPT: FUNCTIONAL PROGRAMMING AND CLASS-BASED OOP 143 Class.create returns a function that can be instantiated. Be sure to assign it to a vari- able ( Player in this case) so that you can use it later. That will be the name of your class. Next, look at initialize. Prototype treats it as the class’s constructor. When the Player class is instantiated (with new Player), this method will be called with the given argu- ments. Ours takes two arguments: firstName and lastName. Finally, look at toString. JavaScript gives a toString property (with a function value) special treatment: if it’s defined, it will be used to convert the object into a string. Firebug explains it better: // redefine the toString method for all arrays (bad idea) Array.prototype.toString = function() { return "#<Array: " + this.join(', ') + '>'; }; var someArray = [1, 2, 3]; // works on both explicit and implicit string conversions: someArray.toString(); //-> "#<Array: 1, 2, 3>" someArray + ''; //-> "#<Array: 1, 2, 3>" So, we’re defining a toString method for reporting the string value of an instance of Player. For now, it’s the player’s full name. This class doesn’t do much, but it’s ready to use. Let’s have some fun: varp=new Player('Andrew', 'Dupont'); p.firstName; //-> "Andrew" p.lastName; //-> "Dupont" p + ''; //-> "Andrew Dupont" p.points; //-> 0 p.scorePoints(6); p.points; //-> 6 Notice how Prototype is mixing some of its own conventions with the native con- structs of JavaScript. As with prototypal OOP, you’ll use the new keyword to generate instances of objects. Prototype’s Class.create method, however, builds a nearly leak- proof abstraction over JavaScript’s inheritance model. You’ll be able to think in terms of class-based OOP, using the same techniques you’d use when architecting Java or Ruby code. The preceding code creates a generic football player with my name. This is exciting for me, as it’s as close as I’ll get to playing professional football. But it’s probably not too exciting for you. All the interesting stuff will be contained in Player’s subclasses. CHAPTER 7 ■ ADVANCED JAVASCRIPT: FUNCTIONAL PROGRAMMING AND CLASS-BASED OOP144 Creating the Subclasses Football players have many things in common, but also many differences. Some are broad and slow; some are tall, skinny, and fast; some are middle-aged and do nothing but kick the ball. We want to repeat ourselves as little as possible, so let’s make sure that all common characteristics are defined in Player. But the differences require that we make a class for each position. A quarterback runs the offense. He can throw the ball to a wide receiver, hand it to a running back, or even run with it himself. var Quarterback = Class.create(Player, { initialize: function($super, firstName, lastName) { // call Player's initialize method $super(firstName, lastName); // define some properties for quarterbacks this.passingYards = 0; this.rushingYards = 0; }, throwPass: function(yards) { console.log(this + ' throws for ' + yards + 'yds.'); this.passingYards += yards; }, throwTouchdown: function(yards) { this.throwPass(yards); console.log('TOUCHDOWN!'); this.scorePoints(6); } }); Notice that the bold parts introduce two new concepts. Just as before, we define the class using Class.create. This time, though, we’ve placed an extra argument at the beginning: the class to be extended. Because Quarter- back is based on Player, it will have all of Player’s properties and methods, plus whatever extra stuff Quarterback defines for itself. Observe how, for instance, Quarterback has a scorePoints method, even though it wasn’t defined on Quarterback directly. That method was inherited fr om Player. So we’v e established a relationship between Player and Quarterback: Player is Quarterback’s superclass, and Quarterback is Player’s subclass. CHAPTER 7 ■ ADVANCED JAVASCRIPT: FUNCTIONAL PROGRAMMING AND CLASS-BASED OOP 145 Quarterback’s initialize method appears to perform some voodoo. As the comments indicate, we’re replacing Player’s initialize method, but we’re still able to call the original method. To do so, we place an argument named $super at the front of initialize’s argu- ment list. The name of the argument serves as a signal that we want to override Player#initialize. The original method is passed into the function as $super, and we call it just like we would call any other method. Keep in mind that initialize still expects two arguments, not three. We define initialize to take three arguments, but the first one is filled in automatically behind the scenes. The public interface has not changed. WHY $SUPER? “Another dollar sign?” you ask. “You’ve got to be kidding me.” No, I’m serious. There are two reasons why Prototype uses the dollar sign ( $) as part of its naming scheme: • As a shortcut for a method that will be used quite often (e.g., $, $$, $A) • As a sigil in front of a word that would otherwise be reserved in JavaScript (e.g., $break, $super) In this case, super is one of the “reserved words” that can’t be used as an identifier (just like you can’t define a function named if or while). Oddly enough, super is not used in JavaScript 1.x, but was preemptively reserved for future use. By adding a dollar sign to the beginning of the word, Prototype circumvents this edict. Because we still have access to the original method, Quarterback#initialize doesn’t have to duplicate code that’s already been written for Player#initialize. We can tell it to call Player#initialize, and give it more instructions afterward. Specifically, Quarterback#initialize sets the starting values for passingYards and rushingYards—statis- tics that aren’t common to all players, and thus need to be defined in subclasses of Player. We can test this code out in a Firebug console: var andrew = new Quarterback('Andrew', 'Dupont'); andrew.passingYards; //-> 0 andrew.points; //-> 0 CHAPTER 7 ■ ADVANCED JAVASCRIPT: FUNCTIONAL PROGRAMMING AND CLASS-BASED OOP146 . kidding me.” No, I’m serious. There are two reasons why Prototype uses the dollar sign ( $) as part of its naming scheme: • As a shortcut for a method that will be used quite often (e.g., $,. bold parts introduce two new concepts. Just as before, we define the class using Class.create. This time, though, we’ve placed an extra argument at the beginning: the class to be extended. Because. classes and instances.) Prototypal inheritance isn’t better or worse than class-based inheritance, but you may not feel that way if you’re not used to it. Most of what you’re used to just isn’t

Ngày đăng: 03/07/2014, 01:20

TỪ KHÓA LIÊN QUAN