问题
Well, I recently learned about closures in Javascript.
While i find it's concept truly amazing, i have yet to find a good application for them, myself.
In all the blog posts, all the tuturials i found, i got a good explanation of what they are and how to work with them.
What i can't find anywhere are examples that make me think: "Wow! you can do THIS with closures? Awesome!!!". All the examples i find are purely academic like this one.
function say667() {
// Local variable that ends up within closure
var num = 666;
var sayAlert = function() { alert(num); }
num++;
return sayAlert;
}
var sayNumber = say667();
alert(sayNumber());
So, i was wondering if any of you can share some mind-blowing experiences with these special kind of functions.
I know that this is sort of an open question, but i will attribute the answer to whoever makes me WOW the most.
Thanks
回答1:
Currying variables
Since "closure" is just a way of saying that a function always retains its original variable scope, there are many ways you can take advantage of that.
Currying seems to be something that people like.
Creating curried value manipulators
Here I've created a function curry
that will return a function that will be used to generate new functions that work with the original curried value.
function curry() {
var args = Array.prototype.slice.call(arguments);
return function(fn) {
return function() {
var args2 = Array.prototype.slice.call(arguments);
return fn.apply(this,args.concat(args2));
};
};
}
Creating a function that curries a string
So if I want to create functions to work with a string, I could curry the string...
var workWithName = curry("Bubba");
...and use the function returned to create new functions that work with the given string in various ways.
Create a function that puts the curried string in a sentence
Here I create a talkToName
function that will incorporate the name into various sentences based on the arguments passed...
var talkToName = workWithName(function(curried_str, before, after) {
return before + curried_str + after;
});
So now I have a talkToName
function that accepts 2 strings that wrap the curried string.
talkToName("Hello there ", ". How are you?"); // "Hello there Bubba. How are you?"
talkToName("", " is really super awesome."); // "Bubba is really super awesome."
Notice that I pass two arguments to the talkToName
function, but the function given to workWithName
accepts 3 arguments.
The first argument is passed by the function we created from workWithName()
, and the two arguments we give to talkToName
are added after the original curried argument.
Create a function increments the characters of the curried string
Here I create an entirely different function using the original workWithName
function that will take the "Bubba" string, and return a string with the letters incremented by the given value...
var incrementName = workWithName(function(curried_str, n) {
var ret = '';
for(var i = 0; i < curried_str.length; i++) {
ret += String.fromCharCode(curried_str[i].charCodeAt() + n);
}
return ret;
});
So I pass my new incrementName
function a number, and it increments the letters in the name, and returns the new string...
incrementName(3); // "Exeed"
incrementName(8); // "J}jji"
incrementName(0); // "Bubba"
So you can see that we gave curry()
a value, and it gave us back a function that can be used to create new functions that work with the original value.
Notice again that I pass one argument to the incrementName
function, but the function given to workWithName
accepts 2 arguments. The first argument is curried.
Other examples with numbers
Here's an example that creates a function generator that works with the numbers 3
and 5
.
var workWith3And5 = curry(3, 5);
Create functions that do various things with the curried numbers
So using the workWith3And5
function, we make a new function that will accept a number argument, and return an Array of the sums of the curried numbers with the given number...
var addNTo3And5 = workWith3And5(function(x, y, n) {
return [3 + n, 5 + n];
});
addNTo3And5( 8 ); // [11, 13];
addNTo3And5( -4 ); // [-1, 1];
And another one using the same workWith3And5
function that curries the numbers 3
and 5
that creates a 3 x 5 Array of Arrays, where the nested Array is given some content...
var create3By5GridWithData = workWith3And5(function(x, y, data) {
var ret = []
for(var i = 0; i < x; i++) {
ret[i] = [];
for(var j = 0; j < y; j++) {
ret[i][j] = data;
}
}
return ret;
});
create3By5GridWithData( 'content' ); // [Array[5], Array[5], Array[5]]
回答2:
Closures are used all the time with callback functions that are called some time later because they allow you to access the local variables of the host calling function or can be used to "freeze" values of local variables into private variables for a particular callback when the local variable itself will be changing to another value as the code continues to execute, but before the callback is called.
Here are examples of closures in answers I've supplied here on SO.
Access parent local variables from setTimeout callback: https://stackoverflow.com/a/7032671/816620
Pass non-static information into a delayed callback: https://stackoverflow.com/a/8660518/816620
I know I've used closures dozens of times in the last month just here in SO answers (I'm just not sure how to quickly find more examples with search without wading through lots of posts).
And, here's a useful closure that creates a private variable:
function slides(images) {
var slideImages = images || [];
// because of this closure, the variable slideImages is available
// to the method defined in here even though the slides function
// has already finished executing
this.addSlide = function(url) {
slideImages.push(url);
}
this.clearSlides = function() {
slideImages = [];
}
}
// the slideImages variable is not available out here
// it is truly private inside the clsoure
var slideshow = new slides(imgArray);
slideshow.addSlide("xxx.jpeg");
回答3:
Well, one neat thing you can do is have private variables:
function Thing() {
var x = 10;
this.getX = function () {
return x;
}
this.increment = function () {
x++;
}
}
Now, when you create a new Thing
, it will have a getX
and increment
method but no way to lower the value of x
. This value of x
will also be unique per instance of Thing
.
Crockford has a page about this pattern: http://javascript.crockford.com/private.html
回答4:
A basic example:
var getDay = (function () {
var days = [
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday',
'Sunday'
];
return function ( n ) {
return days[ n - 1 ];
};
}());
The idea is to assign an IIFE which returns a function to a variable. After this assignment, the variable holds the function that was returned from the IIFE. Since this function was nested inside the IIFE, it has access to all its local variables and arguments, even though the IIFE itself doesn't exist anymore.
So, the whole purpose of the IIFE in the above example was to define an days
array which acts as a private variable of the getDay
function.
回答5:
I used closures to implement lambda expressions for a library I'm working on.
JLinx.Delegate=function() {
var validateArg=function(arg) {
if(typeof arg!=="string"&&typeof arg!=="function"&&arg!==null&&arg!==undefined) {
throw new ArgumentException("arg");
}
};
var funcBody;
function prepBody(code,returnsResult) {
var temp=code.trimLeft().trimRight();
if(returnsResult&&temp.indexOf("return ")== -1) {temp="return "+temp;}
if(temp.substr(temp.length-1,1)!=";") {temp+=";";}
return temp;
}
function getDelegate(arg,defaultLambda,returnsResult) {
validateArg(arg);
if(typeof arg==="function") {return arg;}
arg=(arg===null||arg===undefined)?defaultLambda:arg;
if(arg.indexOf("=>")> -1) {
var parts=arg.split("=>");
var argList=parts[0];
funcBody=prepBody(parts[1],returnsResult);
argList=argList.trimLeft().trimRight()==""?"e":argList;
argList=(argList.indexOf(",")> -1)?argList.split(","):[argList];
switch(argList.length) {
case 1:
return new Function(argList[0],funcBody);
case 2:
return new Function(argList[0],argList[1],funcBody);
default:
throw new InvalidOperationException("Invalid number of arguments to action delegate.");
}
}
else {
funcBody=prepBody(arg,returnsResult);
return new Function("e",funcBody);
}
}
var factory=
{
actionFrom: function(arg) { return getDelegate(arg,"e => return;",false); },
accumulatorFrom: function(arg) { return getDelegate(arg,"e, v => return v;",true); },
comparerFrom: function(arg) { return getDelegate(arg,"l,r=>return l<r?-1:l>r?1:0;",true); },
joinSelectorFrom: function(arg) { return getDelegate(arg,"o, i = { return { o : o, i : i }; };",true); },
predicateFrom: function(arg) { return getDelegate(arg,"e => return true;",true); },
selectorFrom: function(arg) { return getDelegate(arg,"e => return e;",true); }
};
return factory;
} ();
I know this doesn't look like much, but what it allows you to do with the other methods in the library (which actually provides LINQ-to-XML) is write the following:
var exists = myXmlElement.Any("e.name == 'foo' || e.name == 'bar';');
The closure provides a factory that converts a string to a Function that is executed for each element in a Sequence object. If the argument is already a Function, it is returned directly.
That's one thing you can do with closures.
回答6:
It's not really that mind blowing. Languages like Java don't have closures, but you can still write good software with them.
That said, there is a lot of convenience to being able to do something like
var that = this; // that is defined outside of the function below, but is still in its
// lexical scope
arry.each(function(item){
that.doSomething(item); // which means 'that' is "closed-in" to this function
});
回答7:
I think I like this example very much to explain the closures in Javascript..
var juice = "Mango";
var foo = function() {
var juice = "Apple";
return function(){
return juice;
}
};
var juicebar = foo();
console.log(juice);
console.log(juicebar());
来源:https://stackoverflow.com/questions/9058248/good-examples-for-using-a-closure-in-javascript