Higher-order functions used to be a shibboleth of the monks of func-
tional programming, an esoteric term for what seemed like an
advanced programming technique. Nothing could be further from the
truth. Exploiting the concise elegance of functions can often lead to
simpler and more succinct code. Over the years, scripting languages
have adopted these techniques and in the process taken much of the
mystery out of some of the best idioms of functional programming.
Higher-order functions are nothing more than functions that take
other functions as arguments or return functions as their result.
Taking a function as an argument (often referred to as a callback
function because it is “called back” by the higher-order function) is a
particularly powerful and expressive idiom, and one that JavaScript
programs use heavily.
Consider the standard sort method on arrays. In order to work on all
possible arrays, the sort method relies on the caller to determine how
to compare any two elements in an array:
function compareNumbers(x, y) { if (x < y) { return -1; } if (x > y) { return 1; } return 0; } [3, 1, 4, 1, 5, 9].sort(compareNumbers); // [1, 1, 3, 4, 5, 9]
The standard library could have required the caller to pass in an
object with a compare method, but since only one method is required,
taking a function directly is simpler and more concise. In fact, the
above example can be simplified further with an anonymous function:
[3, 1, 4, 1, 5, 9].sort(function(x, y) { if (x < y) { return -1; } if (x > y) { return 1; } return 0; }); // [1, 1, 3, 4, 5, 9]
Learning to use higher-order functions can often simplify your code
and eliminate tedious boilerplate. Many common operations on
arrays have lovely higher-order abstractions that are worth familiar-
izing yourself with. Consider the simple act of transforming an array
of strings. With a loop, we’d write:
var names = ["Fred", "Wilma", "Pebbles"]; var upper = []; for (var i = 0, n = names.length; i < n; i++) { upper[i] = names[i].toUpperCase(); } upper; // ["FRED", "WILMA", "PEBBLES"]
With the handy map method of arrays (introduced in ES5), we
can completely eliminate the loop details, implementing just the
element-by-element transformation with a local function:
var names = ["Fred", "Wilma", "Pebbles"]; var upper = names.map(function(name) { return name.toUpperCase(); }); upper; // ["FRED", "WILMA", "PEBBLES"]
Once you get the hang of using higher-order functions, you can
start identifying opportunities to write your own. The telltale sign
of a higher-order abstraction waiting to happen is duplicate or sim-
ilar code. For example, imagine we found one part of a program con-
structing a string with the letters of the alphabet:
var aIndex = "a".charCodeAt(0); // 97 var alphabet = ""; for (var i = 0; i < 26; i++) { alphabet += String.fromCharCode(aIndex + i); } alphabet; // "abcdefghijklmnopqrstuvwxyz"
Meanwhile, another part of the program generates a string contain-
ing numeric digits:
var digits = ""; for (var i = 0; i < 10; i++) { digits += i; } digits; // "0123456789"
Still elsewhere, the program creates a random string of characters:
var random = ""; for (var i = 0; i < 8; i++) { random += String.fromCharCode(Math.floor(Math.random() * 26) + aIndex); } random; // "bdwvfrtp" (different result each time)
Each example creates a different string, but they all share common
logic. Each loop creates a string by concatenating the results of some
computation to create each individual segment. We can extract the
common parts and move them into a single utility function:
function buildString(n, callback) { var result = ""; for (var i = 0; i < n; i++) { result += callback(i); } return result; }
Notice how the implementation of buildString contains all the com-
mon parts of each loop, but uses a parameter in place of the parts
that vary: The number of loop iterations becomes the variable n ,
and the construction of each string segment becomes a call to the
callback function. We can now simplify each of the three examples to
use buildString :
var alphabet = buildString(26, function(i) { return String.fromCharCode(aIndex + i); }); alphabet; // "abcdefghijklmnopqrstuvwxyz" var digits = buildString(10, function(i) { return i; }); digits; // "0123456789" var random = buildString(8, function() { return String.fromCharCode(Math.floor(Math.random() * 26) + aIndex); }); random; // "ltvisfjr" (different result each time)
There are many benefits to creating higher-order abstractions. If
there are tricky parts of the implementation, such as getting the loop
boundary conditions right, they are localized to the implementation
of the higher-order function. This allows you to fix any bugs in the
logic just once, instead of having to hunt for every instance of the cod-
ing pattern spread throughout your program. If you find you need to
optimize the efficiency of the operation, you again only have one place
where you need to change anything. Finally, giving a clear name such
as buildString to the abstraction makes it clearer to someone reading
the code what the code does, without having to decode the details of
the implementation.
Learning to reach for a higher-order function when you find your-
self repeatedly writing the same patterns leads to more concise code,
higher productivity, and improved readability. Keeping an eye out for
common patterns and moving them into higher-order utility func-
tions is an important habit to develop.
Things to Remember
✦ Higher-order functions are functions that take other functions as
arguments or return functions as their result.
✦ Familiarize yourself with higher-order functions in existing
libraries.
✦ Learn to detect common coding patterns that can be replaced by
higher-order functions.
文章来源于:Effective+Javascript编写高质量JavaScript代码的68个有效方法 英文版