Tải bản đầy đủ (.pdf) (6 trang)

Practical prototype and scipt.aculo.us part 8 potx

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (101.64 KB, 6 trang )

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

×