Javascript function as a parameter to another function?
I'm learning lots of javascript these days, and one of the things I'm not quite understanding is passing functions as parameters to other functions. I get the concept of doing such things, but I myself can't come up with any situations where this would be ideal.
My question is:
When do you want开发者_运维知识库 to have your javascript functions take another function as a parameter? Why not just assign a variable to that function's return value and pass that variable to the function like so:
// Why not do this
var foo = doStuff(params);
callerFunction(foo);
//instead of this
callerFunction(doStuff);
I'm confused as to why I would ever choose to do things as in my second example.
Why would you do this? What are some use cases?
Thanks!!
There are several use cases for this:
1. "Wrapper" functions.
Lets say you have a bunch of different bits of code. Before and after every bit of code, you want to do something else (eg: log, or try/catch exceptions).
You can write a "Wrapper" function to handle this. EG:
function putYourHeadInTheSand(otherFunc) {
try{
otherFunc();
} catch(e) { } // ignore the error
}
....
putYourHeadInTheSand(function(){
// do something here
});
putYourHeadInTheSand(function(){
// do something else
});
2. Callbacks.
Lets say you load some data somehow. Rather than locking up the system waiting for it to load, you can load it in the background, and do something with the result when it arrives.
Now how would you know when it arrives? You could use something like a signal or a mutex, which is hard to write and ugly, or you could just make a callback function. You can pass this callback to the Loader function, which can call it when it's done.
Every time you do an XmlHttpRequest
, this is pretty much what's happening. Here's an example.
function loadStuff(callback) {
// Go off and make an XHR or a web worker or somehow generate some data
var data = ...;
callback(data);
}
loadStuff(function(data){
alert('Now we have the data');
});
3. Generators/Iterators
This is similar to callbacks, but instead of only calling the callback once, you might call it multiple times. Imagine your load data function doesn't just load one bit of data, maybe it loads 200.
This ends up being very similar to a for/foreach loop, except it's asynchronous. (You don't wait for the data, it calls you when it's ready).
function forEachData(callback) {
// generate some data in the background with an XHR or web worker
callback(data1);
// generate some more data in the background with an XHR or web worker
callback(data2);
//... etc
}
forEachData(function(data){
alert('Now we have the data'); // this will happen 2 times with different data each time
});
4. Lazy loading
Lets say your function does something with some text. BUT it only needs the text maybe one time out of 5, and the text might be very expensive to load.
So the code looks like this
var text = "dsakjlfdsafds"; // imagine we had to calculate lots of expensive things to get this.
var result = processingFunction(text);
The processing function only actually needs the text 20% of the time! We wasted all that effort loading it those extra times.
Instead of passing the text, you can pass a function which generates the text, like this:
var textLoader = function(){ return "dsakjlfdsafds"; }// imagine we had to calculate lots of expensive things to get this.
var result = processingFunction(textLoader);
You'd have to change your processingFunction
to expect another function rather than the text, but that's really minor. What happens now is that the processingFunction
will only call the textLoader
the 20% of the time that it needs it. The other 80% of the time, it won't call the function, and you won't waste all that effort.
4a. Caching
If you've got lazy loading happening, then the textLoader
function can privately store the result text in a variable once it gets it. The second time someone calls the textLoader
, it can just return that variable and avoid the expensive calculation work.
The code that calls textLoader
doesn't know or care that the data is cached, it's transparently just faster.
There are plenty more advanced things you can do by passing around functions, this is just scratching the surface, but hopefully it points you in the right direction :-)
One of the most common usages is as a callback. For example, take a function that runs a function against every item in an array and re-assigns the result to the array item. This requires that the function call the user's function for every item, which is impossible unless it has the function passed to it.
Here is the code for such a function:
function map(arr, func) {
for (var i = 0; i < arr.length; ++i) {
arr[i] = func(arr[i]);
}
}
An example of usage would be to multiply every item in an array by 2:
var numbers = [1, 2, 3, 4, 5];
map(numbers, function(v) {
return v * 2;
});
// numbers now contains 2, 4, 6, 8, 10
You would do this if callerFunction
wants to call doStuff
later, or if it wants to call it several times.
The typical example of this usage is a callback function, where you pass a callback to a function like jQuery.ajax
, which will then call your callback when something finishes (such as an AJAX request)
EDIT: To answer your comment:
function callFiveTimes(func) {
for(var i = 0; i < 5; i++) {
func(i);
}
}
callFiveTimes(alert); //Alerts numbers 0 through 4
Passing a function as a parameter to another function is useful in a number of situations. The simplest is a function like setTimeout
, which takes a function and a time and after that time has passed will execute that function. This is useful if you want to do something later. Obviously, if you called the function itself and passed the result in to the setTimeout
function, it would have already happened and wouldn't happen later.
Another situation this is nice is when you want to do some sort of setup and teardown before and after executing some blocks of code. Recently I had a situation where I needed to destroy a jQuery UI accordion, do some stuff, and then recreate the accordion. The stuff I needed to do took a number of different forms, so I wrote a function called doWithoutAccordion(stuffToDo)
. I could pass in a function that got executed in between the teardown and the setup of the accordion.
Callbacks. Say you're doing something asynchronous, like an AJAX call.
doSomeAjaxCall(callbackFunc);
And in doSomeAjaxCall(), you store the callback to a variable, like var ajaxCallback
Then when the server returns its result, you can call the callback function to process the result:
ajaxCallback();
This probably won't be of much practical use to you as a web programmer, but there is another class of uses for functions as first-class objects that hasn't come up yet. In most functional languages, like Scheme and Haskell, passing functions around as arguments is, along with recursion, the meat-and-potatoes of programming, rather than something with an occasional use. Higher-order functions (functions that operate on functions) like map and fold enable extremely powerful, expressive, and readable idioms that are not as readily available in imperative languages.
Map is a function that takes a list of data and a function and returns a list created by applying that function to each element of the list in turn. So if I wanted to update the positions of all the bouncing balls in my bouncing ball simulator, instead of
for(ball : ball_list) {
ball.update();
ball.display();
}
I would instead write (in Scheme)
(display (map update ball-list))
or in Python, which offers a few higher-order functions and a more familiar syntax,
display( map(update, ball-list) )
Fold takes a two-place function, a default value, and a list, and applies the function to the default and the first element, then to the result of that and the second element, and so on, finally returning the last value returned. So if my server is sending in batches of account transactions, instead of writing
for(transaction t : batch) {
account_balance += t;
}
I would write
(fold + (current-account-balance) batch))
These are just the simplest uses of the most common HOFs.
I will illustrate is with sort scenario.
Let's assume that you have an object to represent Employee of the company. Employee has multiple attributes - id, age, salary, work-experience etc.
Now, you want to sort a list of employees - in one case by employee id, in another case by salary and in yet another case by age.
Now the only thing that you wish to change is how to compare.
So, instead of having multiple sort methods, you can have a sort a method that takes a reference to function that can do the comparison.
Example code:
function compareByID(l, r) { return l.id - r.id; }
function compareByAge(l, r) { return l.age - r.age; }
function compareByEx(l, r) { return l.ex - r.ex; }
function sort(emps, cmpFn) {
//loop over emps
// assuming i and j are indices for comparision
if(cmpFn(emps[i], emps[j]) < 0) { swap(emps, i, j); }
}
精彩评论