Question: There seem to be many benefits to Closures, but what are the negatives (memory leakage? obfuscation problems? bandwidth increasage?)? Additionally, is my understanding of Closures correct? Finally, once closures are created, can they be destroyed?
I've been reading a little bit about Javascript Closures. I hope someone a little more knowledgeable will guide my assertions, correcting me where wrong.
Benefits of Closures:
What I've found helpful is to do some basic testing, regarding local/global scope:
<script type="text/javascript">
var global_text = "";
var global_count = 0;
var global_num1 = 10;
var global_num2 = 20;
var global_num3 = 30;
function outerFunc() {
var local_count = local_count || 0;
alert("global_num1: " + global_num1); // global_num1: undefined
var global_num1 = global_num1 || 0;
alert("global_num1: " + global_num1); // global_num1: 0
alert("global_num2: " + global_num2); // global_num2: 20
global_num2 = global_num2 || 0; // (notice) no definition with 'var'
alert("global_num2: " + global_num2); // global_num2: 20
global_num2 = 0;
alert("local_count: " + local_count); // local_count: 0
function output() {
global_num3++;
alert("local_count: " + local_count + "\n" +
"global_count: " + global_count + "\n" +
"global_text: " + global_text
);
local_count++;
}
local_count++;
global_count++;
return output;
}
var myFunc = outerFunc();
myFunc();
/* Outputs:
**********************
* local_count: 1
* global_count: 1
* global_text:
**********************/
global_text = "global";
myFunc();
/* Outputs:
**********************
* local_count: 2
* global_count: 1
* global_text: global
**********************/
var local_count = 100;
myFunc();
/* Outputs:
**********************
* local_count: 3
* global_count: 1
* global_text: global
**********************/
alert("global_num1: " + global_num1); // global_num1: 10
alert("global_num2: " + global_num2); // global_num2: 0
alert("global_num3: " + global_num3); // global_num3: 33
</script>
Interesting things I took out of it:
The alerts in outerFunc are only called once, which is when the outerFunc call is assigned to myFunc (myFunc = outerFunc()). This assignment seems to keep the outerFunc open, in what I would like to call a persistent state.
Everytime myFunc is called, the return is executed. In this case, the return is the internal function.
Something really interesting is the localization that occurs when defining local variables. Notice the difference in the first alert between global_num1 and global_num2, even before the variable is trying to be created, global_num1 is considered undefined because the 'var' was used to signify a local variable to that function. -- This has been talked about before, in the order of operation for the Javascript engine, it's just nice to see this put to work.
Globals can still be used, but local variables will override them. Notice before the third myFunc call, a global variable called local_count is created, but it as no effect on the internal function, which has a variable that goes by the same name. Conversely, each function call has the ability to modify global variables, as noticed by global_var3.
Post Thoughts: Even though the code is straightforward, it is cluttered by alerts for you guys, so you can plug and play.
I know there are other examples of closures, many of which use anonymous functions in combination with looping structures, but I think this is good for a 101-starter course to see the effects.
The one thing I'm concerned with is the negative impact closures will have on memory. Because it keeps the function environment open, it is also keeping those variables stored in memory, which may/may not have performance implications, especially regarding DOM traversals and garbage collection. I'm also not sure what kind of role this will play in terms of memory leakage and I'm not sure if the closure can be removed from memory by a simple "delete myFunc;."
Hope this helps someone,
vol7ron
A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function's scope from an inner function.
The advantage of closures in javascript is that it allows you to bind a variable to an execution context. var closedIn = {}; var f = function(){ closedIn. blah = 'blah'; // closedIn was just "closed in" because I used in the function, but it was defined outside the function. }
A closure is simply a more convenient way to give a function access to local state. Rather than having to create a class which knows about the local variable you want the function to use, you can simply define the function on the spot, and it can implicitly access every variable that is currently visible.
Closures are one of the most powerful features of JavaScript. JavaScript allows for the nesting of functions and grants the inner function full access to all the variables and functions defined inside the outer function (and all other variables and functions that the outer function has access to).
Closures bring a lot of benefits...but also a number of gotchas. The same thing that makes them powerful also makes them quite capable of making a mess if you're not careful.
Besides the issue with circular references (which isn't really as much of a problem anymore, since IE6 is hardly used at all outside of China), there's at least one other huge potential negative: They can complicate scope. When used well, they improve modularity and compatibility by allowing functions to share data without exposing it...but when used badly, it can become difficult if not impossible to trace exactly where a variable is set or changed.
JavaScript without closures has three* scopes for variables: block-level, function-level, and global. There is no object-level scope. Without closures, you know a variable is either declared in the current function, or in the global object (because that's where global variables live).
With closures, you no longer have that assurance. Each nested function introduces another level of scope, and any closures created within that function see (mostly) the same variables as the containing function does. The big problem is that each function can define its own variables at will that hide the outer ones.
Using closures properly requires that you (a) be aware of how closures and var
affect scope, and (b) keep track of which scope your variables are in. Otherwise, variables can be accidentally shared (or pseudo-variables lost!), and all sorts of wackiness can ensue.
Consider this example:
function ScopeIssues(count) {
var funcs = [];
for (var i = 0; i < count; ++i) {
funcs[i] = function() { console.log(i); }
}
return funcs;
}
Short, straightforward...and almost certainly broken. Watch:
x = ScopeIssues(10);
x[0](); // outputs 10
x[1](); // does too
x[2](); // same here
x[3](); // guess
Every function in the array outputs count
. What's going on here? You're seeing the effects of combining closures with a misunderstanding of closed-over variables and scope.
When the closures are created, they're not using the value of i
at the time they were created to determine what to output. They're using the variable i
, which is shared with the outer function and is still changing. When they output it, they're outputting the value as of the time it is called. That will be equal to count
, the value that caused the loop to stop.
To fix this before let
existed, you'd need another closure.
function Corrected(count) {
var funcs = [];
for (var i = 0; i < count; ++i) {
(function(which) {
funcs[i] = function() { console.log(which); };
})(i);
}
return funcs;
}
x = Corrected(10);
x[0](); // outputs 0
x[1](); // outputs 1
x[2](); // outputs 2
x[3](); // outputs 3
As of ES7, you can use let
instead of var
, and each iteration of the loop will basically get its own version of i
.
function WorksToo(count) {
var funcs = [];
for (let i = 0; i < count; ++i) {
funcs[i] = function() { console.log(i); }
}
return funcs;
}
x = WorksToo(10);
x[0](); // outputs 0
x[1](); // outputs 1
x[2](); // outputs 2
x[3](); // outputs 3
But that comes with complications of its own -- variables with the same name and purpose, in the same block of code, are now effectively disconnected. So you don't want to just always use let
either. The only real fix is to all-around be much more aware of scope.
Another example:
value = 'global variable';
function A() {
var value = 'local variable';
this.value = 'instance variable';
(function() { console.log(this.value); })();
}
a = new A(); // outputs 'global variable'
this
and arguments
are different; unlike nearly everything else, they are not shared across closure boundaries?. Every function call redefines them -- and unless you call the function like
obj.func(...)
,func.call(obj, ...)
,func.apply(obj, [...])
, orvar obj_func = func.bind(obj); obj_func(...)
to specify a this
, then you'll get the default value for this
: the global object.^
The most common idiom to get around the this
issue is to declare a variable and set its value to this
. The most common names i've seen are that
and self
.
function A() {
var self = this;
this.value = 'some value';
(function() { console.log(self.value); })();
}
But that makes self
a real variable, with all the potential oddness that entails. Fortunately, it's rare to want to change the value of self
without redefining the variable...but within a nested function, redefining self
of course redefines it for all the functions nested within it as well. And you can't do something like
function X() {
var self = this;
var Y = function() {
var outer = self;
var self = this;
};
}
because of hoisting. JavaScript effectively moves all the variable declarations to the top of the function. That makes the above code equivalent to
function X() {
var self, Y;
self = this;
Y = function() {
var outer, self;
outer = self;
self = this;
};
}
self
is already a local variable before outer = self
runs, so outer
gets the local value -- which at this point, is undefined
. You've just lost your reference to the outer self
.
* As of ES7. Previously, there were only two, and variables were even easier to track down. :P
? Functions declared using lambda syntax (new to ES7) don't redefine this
and arguments
. Which potentially complicates the matter even more.
^ Newer interpreters support a so-called "strict mode": an opt-in feature that aims to make certain iffy code patterns either fail entirely or cause less damage. In strict mode, this
defaults to undefined
rather than the global object. But it's still some whole other value than you usually intended to mess with.
You may get a raft of good answers. One certain negative is the Internet Explorer circular reference memory leak. Basically, "circular" references to DOM objects are not recognized as collectible by JScript. It's easy to create what IE considers a circular reference using closures. Several examples are provided in the second link.
In IE6, the only way to reclaim the memory is to terminate the whole process. In IE7 they improved it so that when you navigate away from the page in question (or close it), the memory is reclaimed. In IE8, DOM objects are better understood by JScript and are collected as you'd expect they should be.
The suggested workaround for IE6 (besides terminating the process!) is not to use closures.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With