Practical prototype and scipt.aculo.us part 8 potx

6 264 0
Practical prototype and scipt.aculo.us part 8 potx

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

Thông tin tài liệu

Summary You can do far more with Prototype than what I’ve just described, but the functions in this chapter are the ones you’ll use most often. And although they solve common prob- lems, they also form the foundation for a general scripting philosophy: one that espouses fewer lines of code, separation of content and behavior, and the principle of least sur- prise. Later on, you’ll learn how to use these functions within a set of conventions to make your DOM scripting experience far more pleasant. CHAPTER 2 ■ PROTOTYPE BASICS 29 Collections (Or, Never Write a for Loop Again) Collections are at the heart of DOM scripting—arrays, hashes, DOM NodeLists, and various other groups of items. Nearly all your scripts will do some form of iteration over an array. So why is iteration so bland in JavaScript? Prototype sports a robust library for dealing with collections. It makes arrays astoundingly flexible (and invents Hash, a subclass of Object, for key/value pairs), but can also be integrated into any collections you use in your own scripts. The Traditional for Loop Amazingly, the first version of JavaScript didn’t even support arrays. They were added soon after, but with only one real enhancement over a vanilla Object—a magic length property that would count the number of numeric keys in the array. For example var threeStooges = new Array(); threeStooges[0] = "Larry"; threeStooges[1] = "Curly"; threeStooges[2] = "Moe"; console.log(threeStooges.length); //-> 3 The length property and the ubiquitous for looping construct result in a simple, low- tech way to loop over an array’s values: start at 0 and count up to the value of length. for (var i = 0; i < threeStooges.length; i++) { console.log(threeStooges[i] + ": Nyuk!"); } 31 CHAPTER 3 This is a fine and decent way to loop, but JavaScript is capable of so much more! A language with JavaScript’s expressive power can embrace functional programming concepts to make iteration smarter. Functional Programming JavaScript is a multi-paradigm language. It can resemble the imperative style of C, the object-oriented style of Java, or the functional style of Lisp. To illustrate this, let’s define a function and see what it can do: function makeTextRed(element) { element.style.color = "red"; } This function expects a DOM element node and does exactly what it says: it turns the node’s enclosed text red. To apply this function to an entire collection of nodes, we can use the venerable for loop: var paragraphs = $$('p'); for (var i = 0; i < elements.length; i++) makeTextRed(elements[i]); But let’s look at this from another angle. You learned in Chapter 1 that functions in JavaScript are “first-class objects,” meaning that they can be treated like any other data type, like so: typeof makeTextRed //-> "function" makeTextRed.constructor; //-> Function var alias = makeTextRed; alias == makeTextRed; //-> true In short, anything that can be done with strings, numbers, or other JavaScript data types can be done with functions. This enables a different approach to iteration: since functions can be passed as arguments to other functions, you can define a function for iterating over an array. Again, this is easier to explain with code than with words: function each(collection, iterator) { for(vari=0;i<collection.length; i++) iterator(collection[i]); } CHAPTER 3 ■ COLLECTIONS (OR, NEVER WRITE A FOR LOOP AGAIN)32 The iterator argument is a function. The each method we just wrote will loop over an array’s indices and call iterator on each, passing into it the current item in the array. Now we can iterate thusly: var paragraphs = $$('p'); each(paragraphs, makeTextRed); Also remember from Chapter 1 that functions have a literal notation—you don’t have to name a function before you use it. If we won’t use the makeTextRed function anywhere else in the code, then there’s no reason to define it beforehand. each(paragraphs, function(element) { element.style.color = "red"; }); We can make one more improvement to our code. Since each is a method made to act on arrays, let’s make it an instance method of all arrays: Array.prototype.each = function(iterator) { for (var i = 0; i < this.length; i++) iterator(this[i]); }; Remember that this refers to the execution scope of the function—in this case, it’s the array itself. Now we can write the following: paragraphs.each(function(element) { element.style.color = "red"; }); To look at this more broadly, we’ve just abstracted away the implementation details of iteration. Under the hood, we’re calling the same old for loop, but because we’ve built a layer on top, we’re able to define other functions that involve iterating but do much more than the preceding each example. ABOUT FUNCTION NOTATION This book uses a common notation to distinguish between static methods and instance methods. Static methods are marked with a dot—for example, Array.from refers to the from method on the Array object. Instance methods are marked with an octothorpe: Array#each refers to the each method on Array.prototype—that is, a method on an instance of Array. CHAPTER 3 ■ COLLECTIONS (OR, NEVER WRITE A FOR LOOP AGAIN) 33 Prototype’s Enumerable Object Prototype defines a handful of functions in an object called Enumerable. Anything that is “enumerable” (anything that can be iterated over) can use these methods. These functions include each (much like the one we defined previously) and many other methods that all hook into each internally. These methods aren’t specific to arrays—they can be used on any collection, as long as we tell them how to enumerate the items therein. Prototype automatically extends Enumerable onto Array. At the end of the chapter, you’ll learn how to implement Enumerable in your own classes, but for now we’ll use arrays for all our examples. Using Enumerable#each Enumerable#each is the foundation that the rest of Enumerable relies upon, so let’s take a closer look at it. I’ve been defaming for loops for several pages now, but they do have one critical advantage over functional iteration: they let you short-circuit the iteration flow by using the keywords break (abort the loop) and continue (skip to the next item in the loop). We need a way to emulate these keywords if we want to match the feature set of traditional loops. var elements = $$('.menu-item'); // find the element whose text content contains "weblog" for (var i = 0, element; element = elements[i]; i++) { if (!element.id) continue; if (element.innerHTML.include('weblog')) break; } Simulating continue is easy enough—an empty return within a function will do the trick: var elements = $$('.menu-item'), weblogElement; elements.each( function(element) { if (!element.id) return; /* */ }); But a break equivalent takes a bit of voodoo. Prototype makes smart use of excep- tions to pull this off. It creates a $break object that can be thrown within loops to exit immediately. CHAPTER 3 ■ COLLECTIONS (OR, NEVER WRITE A FOR LOOP AGAIN)34 var elements = $$('.menu-item'), weblogElement; elements.each( function(element) { if (!element.id) return; if (element.innerHTML.include('weblog')) { weblogElement = element; throw $break; } }); In this example, we “throw” the $break object as though it were an exception. It interrupts the execution of the function and gets “caught” higher in the call stack, at which point the each method stops iterating and moves on. Now we’re even. It’s rare that you’ll need to use $break—most of the use cases for breaking out of loops are addressed by other Enumerable methods—but it’s comforting to know it’s there. Finding Needles in Haystacks: detect, select, reject, and partition The code pattern we used in the last section—finding one needle in a haystack—is a common one, but we can express it more concisely than through an each loop. The function we pass into each serves as an item manipulator, but we can also use that function as a litmus test to let us know whether an item matches our needle. The next four methods do just this. Using Enumerable#detect Enumerable#detect finds and returns one item in your collection. It takes a function as an argument (one that returns true or false) and will return the first item in the collec- tion that causes the function to return true. function isEven(number) { return number % 2 == 0; } [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].detect(isEven); //-> 2 If there ar e no matches, detect will return false. CHAPTER 3 ■ COLLECTIONS (OR, NEVER WRITE A FOR LOOP AGAIN) 35 . we can also use that function as a litmus test to let us know whether an item matches our needle. The next four methods do just this. Using Enumerable#detect Enumerable#detect finds and returns. scripting philosophy: one that espouses fewer lines of code, separation of content and behavior, and the principle of least sur- prise. Later on, you’ll learn how to use these functions within a set. over an array. So why is iteration so bland in JavaScript? Prototype sports a robust library for dealing with collections. It makes arrays astoundingly flexible (and invents Hash, a subclass of Object,

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

Tài liệu cùng người dùng

Tài liệu liên quan