What are the closures for exactly?
I have been reading about closures and javascript, and I thought I got it, till I tried this:
var Object5 = function (param) {
var x = 0;
var squareBrace = function () {
return '[' + param + x + ']';
};
this.toString = function () {
x = x + 1;
return squareBrace();
};
};
Then I ran this code:
var counter = new Object5("Counter: ");
print("Has x:" + ('x' in counter));
print("Has f:" + ('f' in counter));
print("Can access x:" + (!!counter.x));
print("Can Invoke f:" 开发者_如何学运维+ (!!counter.f));
print(counter.toString());
print(counter.toString());
print(counter.toString());
print(counter.toString());
print(counter.toString());
print(counter.toString());
And that is what I got:
Has x:false
Has f:false
Can access x:false
Can Invoke f:false
[Counter: 1]
[Counter: 2]
[Counter: 3]
[Counter: 4]
[Counter: 5]
[Counter: 6]
I thought I would get a 'TypeError' because 'x' and 'f' would be 'undefined', but then I got it working. I thought closures were for enable this behavior, 'x' and 'y' are 'private' and without a closure those member would be forgotten.
Apparently I got it all wrong, or I am missing something important here.
Could please somebody tell me what are closures for then and why this work?
Thanks.
In order to tackle the topic of closures, we have to go over how variable scope works in JavaScript. Let's start by looking at a basic function and the related problems:
function uniqueInteger(){
var counter = 0;
return ++counter;
}
console.log(uniqueInteger()); // returns '1'
console.log(counter); // undefined
This function declares and assigns a value 0 to the variable counter
, and then returns that value incremented. As we can see, while the function is executing, the variable counter is accessible within the function. However once the function returns, the variable is no longer defined, as it is part of the scope of the uniqueInteger()
function. We could also say that the counter
variable is private; only the function uniqueInteger()
can access it.
But what if we wanted to "remember" that variable's value so that next time this function is invoked, counter
starts off at 1, and not 0. One approach would be to declare the variable counter
outside of the function, but then it would no longer be private, and other functions can then change the counter variable.
In order to get around this problem, we need to look at how functions work. When a function is invoked (as seen above), its variables cease to exist after its returned. But within the confines of that function, any nested function will still be able to access its parent function's "private" variables:
function uniqueInteger(){
var counter = 0;
// an example of nested function accessing the counter variable:
return (function() { return ++counter })();
}
console.log(uniqueInteger()); // returns '1'
console.log(counter); // undefined
*Note that the number of invocations with brackets () coincides to the number of functions defined. The nested function is self-invoked, and the outside function is invoked with the line console.log(uniqueInteger());
Therefore the same function can be written as:
function uniqueInteger(){
var counter = 0;
// an example of nested function accessing the counter variable:
return function() { return ++counter };
}
console.log(uniqueInteger()()); // returns '1'
console.log(counter); // undefined
As we can see, the nested function still has access the variable counter and the uniqueInteger()
function still returns "1". Although the counter variable still disappears after uniqueInteger()
returns (we'll solve that problem later), the fact that the nested function has access to counter
gives it the ability to do things to this variable, and then return the result. Whenever uniqueInteger()
is called, the same "scope chain" will exist. Very simplistically speaking this is referred to as Lexical Scope.
Now let's talk about that problem of the variable counter disappearing after the function returns. What happens here is a result of a feature of JavaScript called garbage collection. When a function and its variables are no longer in use, they get "thrown out". But if there is a reference to the return value of that function, (if that function is assigned to an outside variable, for example), it gets "remembered" along with any variables inside it according to that "scope chain" we talked about above:
function uniqueInteger(){
var counter = 0;
return function() { return ++counter };
}
var uniqueInt = uniqueInteger();
console.log(uniqueInt()); // returns '1'
console.log(uniqueInt()); // returns '2'
console.log(counter) // still "undefined"
So what happened? Because we "saved" the return value of the nested function inside an outside variable, the variable counter
did not get garbage collected, and the next time uniqueInt()
was invoked, counter was still equal to 1. We can also say that its state was saved. We achieved our goal: we made a function that remembered its variable between invocations with out defining its variables outside of it, keeping them private.
We can rewrite this function above as a function definition expression:
var uniqueInteger = (function(){
var counter = 0;
return function() { return ++counter };
})();
console.log(uniqueInteger()); // returns '1'
console.log(uniqueInteger()); // returns '2'
console.log(counter) // still "undefined"
Note that there are still two functions, and therefore two corresponding invocations. Only this time the outside function is self-invoked, in order to save its return value, not just the function itself, to the variable uniqueInteger
. Otherwise the return value of the variable uniqueInteger
would be function () { return ++counter; }
. The technique described above is essentially what closures are: using functions (*or objects) inside functions to operate on internal values that save their state between invocations while keeping them private.
*You can also return objects that have functions that operate on the values of the outside function:
var uniqueInteger = (function(){
var counter = 0;
return {
value: function() { return counter },
count: function() { return ++counter },
reset: function() { counter = 0; return counter;}
};
})();
console.log(uniqueInteger.value()); // 0
uniqueInteger.count();
console.log(uniqueInteger.value()); // 1
uniqueInteger.reset()
console.log(uniqueInteger.value()); // again 0
This pattern allows you to have a self-contained function object that operates on its own private variables with out the risk of name collision or malicious tampering from outside.
I had a hard time grasping closures too but if you keep reading over the literature and SO answers you'll eventually get it. Just keep playing around with your code :)
A closure is a scoping technique. It's a way of pulling a parameter defined in one scope into another scope. When you do
var x = 1;
var f = function(){
console.log(x); // you've just 'closed in' the variable x.
};
f();
x
will be available in the function even though its not declared in the function.
In your specific instance, 2 variables are closed-in: param
and x
. They are bound to the scope of the functions you defined. When you execute toString
, your are incrementing the closed in x
. When toString
executes squareBrace
, that method uses both x
and param
. So you have 2 closures around the variable x
, one for each method (its also in the scope of the object itself)
精彩评论