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

Practical prototype and scipt.aculo.us part 9 ppsx

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 (86.71 KB, 6 trang )

Using Enumerable#select
What if we need to find several needles in a haystack?
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].select(isEven);
//-> [2, 4, 6, 8, 10]
Just like detect, select tests each item against the given function. But it doesn’t stop
after the first match—it will return all items in the collection that match the criteria.
["foo", 1, "bar", "baz", 2, null].select( function(item) {
return typeof item === "string";
});
//-> ["foo", "bar", "baz"]
Unlike detect, which is guaranteed to return only one item, select will always return
an array. If there are no matches, it will return an empty array.
Using Enumerable#reject
Nearly identical to select, reject will return all the items that fail a particular test.
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].reject(isEven);
//-> [1, 3, 5, 7, 9]
Using Enumerable#partition
When you need to separate a collection into two groups, use Enumerable#partition.It
returns a two-item array: the first an array of all items that passed the test, and the sec-
ond an arr
ay of all items that failed the test.
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].partition(isEven);
//-> [ [2, 4, 6, 8, 10], [1, 3, 5, 7, 9] ]
Sorting Collections: min, max, and sortBy
The next three Enumerable methods—min, max, and sortBy—address common situations of
arranging collections by value.
CHAPTER 3 ■ COLLECTIONS (OR, NEVER WRITE A FOR LOOP AGAIN)36
Using Enumerable#min and #max
Much like Math.min and Math.max, which identify the smallest and largest values of all the
arguments passed to them,
Enumerable#min and #max will find the smallest and largest val-


ues in an existing group:
Math.min(1, 4, 9, 16, 25); //-> 1
Math.max(1, 4, 9, 16, 25); //-> 25
var squares = [1, 4, 9, 16, 25];
squares.min(); //-> 1
squares.max(); //-> 25
In this example, it’s easy to figure out what the minimum and maximum values
are—numbers are directly comparable. For trickier collections, though, you’ll need to
pass in a function to identify exactly what you want the maximum or minimum of:
var words = ["flowers", "the", "hate", "moribund", "sesquicentennial"];
words.max( function(word) { return word.length; } ); //-> 16
words.min( function(word) { return word.length; } ); //-> 3
Comparing on string length, we get 3 and 16 as the min and max, respectively—the
lengths of the shortest and longest words in the array.
Using Enumerable#sortBy
JavaScript has a built-in sorting method: Array#sort. Why do we need another?
Let’s illustrate. If we try to use
Array#sort on an example set of numbers, we’ll be
in for a surprise:
[2, 5, 4, 8, 9, 1, 3, 10, 7, 6].sort();
//-> [1, 10, 2, 3, 4, 5, 6, 7, 8, 9]
As it happens, sort called with no arguments will coerce the array items into strings
before it compares them. (
10 is greater than 2, but "10" is less than "2".) If we want to
compare the numbers directly, we must pass a function argument into
sort:
[2, 5, 4, 8, 9, 1, 3, 10, 7, 6].sort(function(a, b) {
return a – b;
});
//-> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

CHAPTER 3 ■ COLLECTIONS (OR, NEVER WRITE A FOR LOOP AGAIN) 37
The passed function tells sort how to compare any two items in the array. In the pre-
ceding example, if
a is greater than b, then the return value will be positive, indicating
that
a should follow b.If b is greater than a, then the return value will be negative, and a
will precede b. (If the return value is 0, then the two are equal, of course.)
This is nuts—or, at the very least, surprising. We need a better sort function.
Enumerable#sortBy works a little differently. It, too, takes a function argument, but
the function is used only to translate a given item to a comparison value:
var words = ["aqueous", "strength", "hated", "sesquicentennial", "area"];
// sort by word length
words.sortBy( function(word) { return word.length; } );
//-> ["area", "hated", "aqueous", "strength", "sesquicentennial"]
// sort by number of vowels in the word
words.sortBy( function(word) { return word.match(/[aeiou]/g).length; } )
//-> ["strength", "hated", "area", "aqueous", "sesquicentennial"]
As you can see, the comparison function takes one argument, rather than two. Most
developers will find this far more intuitive.
Advanced Enumeration: map, inject, invoke,
and pluck
The next four Enumerable methods carry more cryptic names, but are every bit as useful
as the methods described previously.
Using Enumerable#map and Enumerable#inject
The map method performs parallel transformation. It applies a function to every item in
a collection, pushes each result into an array, and then returns that array.
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map( function(num) { return num * num; } );
//-> [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
The inject method returns an accumulated collection. Think of it as a hat being
passed around to each of the items—each item throws a different quantity into the hat.

CHAPTER 3 ■ COLLECTIONS (OR, NEVER WRITE A FOR LOOP AGAIN)38
var words = ["aqueous", "strength", "hated", "sesquicentennial", "area"];
var totalLength = words.inject(0, function(memo, string) {
return memo + string.length;
});
// -> 40
Since we need to keep track of memo—the variable that stores our running total—the
arguments for
inject are slightly different. The first argument of inject is our starting
value—
0, in this case, since we’re dealing with a numeric property. The second argument
is the function we’re using against the collection’s items.
This inner function itself takes two arguments: our running total (
memo) and the item
we’re working with (
string). The function’s return value will be used as the memo for the
next item in the collection.
This can be a bit confusing, so let’s add a logging statement to the inner function to
illustrate what’s going on:
var totalLength = words.inject(0, function(memo, string) {
console.log('received ' + memo + '; added ' + string.length);
console.log('returning ' + (memo + string.length));
return memo + string.length;
});
//-> received 0; added 7
//-> returning 7
//-> received 7; added 8
//-> returning 15
//-> received 15; added 5
//-> returning 20

//-> received 20; added 16
//-> returning 36
//-> received 36; added 4
//-> returning 40
//-> 40
Now that you can see each step in the enumeration, the behavior of inject should be
easier to follow.
CHAPTER 3 ■ COLLECTIONS (OR, NEVER WRITE A FOR LOOP AGAIN) 39
Using Enumerable#pluck and Enumerable#invoke
These two Enumerable methods are somewhat special. They take a string as their first
argument instead of a function.
The
pluck method collects individual properties on each of the objects on the
collection:
var words = ["aqueous", "strength", "hated", "sesquicentennial", "area"];
words.pluck('length');
//-> [7, 8, 5, 16, 4]
Note that this example code is equivalent to
words.map( function(word) { return word.length; } );
but is shorter and more meaningful.
The
invoke method is similar: it calls the specified instance method on each item.
Let’s illustrate by using one of Prototype’s string methods:
" aqueous ".strip(); //-> "aqueous"
var paddedWords = [" aqueous ", "strength ", " hated ",
"sesquicencennial", " area "];
words.invoke('strip');
//-> ["aqueous", "strength", "hated", "sesquicentennial", "area"]
This code is equivalent to
words.map( function(word) { return word.strip(); } );

but invoke can also pass arguments along to the instance method. Simply add the
required number of arguments after the first:
"swim/swam".split('/'); //-> ["swim", "swam"]
"swim/swam".replace('/', '|'); //-> "swim|swam"
var wordPairs = ["swim/swam", "win/lose", "glossy/matte"];
wordPairs.invoke('split', '/');
//-> [ ["swim", "swam"], ["win", "lose"], ["glossy", "matte"] ]
wordPairs.invoke('replace', '/', '|');
//-> ["swim|swam", "win|lose", "glossy|matte"]
The map, inject, pluck, and invoke methods greatly simplify four very common code
patterns. Become familiar with them and you’ll start to notice uses for them all over the
code you write.
CHAPTER 3 ■ COLLECTIONS (OR, NEVER WRITE A FOR LOOP AGAIN)40
Other Collections That Use Enumerable
Two other Prototype classes that make use of Enumerable are Hash and ObjectRange.
Together they serve as great examples of how to use
Enumerable with other types of
collections.
Hash
There is no built-in facility in JavaScript for setting key/value pairs—the construct that’s
known as a hash (in Ruby), a dictionary (in Python), or an associative array (in PHP).
There is, of course, an ordinary object, and this suffices for most cases.
var airportCodes = {
AUS: "Austin-Bergstrom Int'l",
HOU: "Houston/Hobby",
IAH: "Houston/Intercontinental",
DAL: "Dallas/Love Field",
DFW: "Dallas/Fort Worth"
};
for (var key in airportCodes) {

console.log(key + " is the airport code for " + airportCodes[key] + '.');
}
>>> AUS is the airport code for Austin-Bergstrom Int'l.
>>> HOU is the airport code for Houston/Hobby.
>>> IAH is the airport code for Houston/Intercontinental.
>>> DAL is the airport code for Dallas/Love Field.
>>> DFW is the airport code for Dallas/Fort Worth.
We can declare an object and iterate over its properties quite easily. This doesn’t get
us everything a hash would, but it comes very close.
Eventually, however, we’ll run into two major problems.
Objects Have No Key Safety
An object is not a blank slate when it’s declared. It has native properties and methods
with names that may conflict with the names you’d want to use for your keys.
CHAPTER 3 ■ COLLECTIONS (OR, NEVER WRITE A FOR LOOP AGAIN) 41

×