Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>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.</p> <p>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: <strong>They can complicate scope.</strong> 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.</p> <p>JavaScript without closures has three<sup>*</sup> 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).</p> <p>With closures, you no longer have that assurance. Each nested function introduces another level of scope, and any closures created within that function see (<em>mostly</em>) 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. </p> <p>Using closures properly requires that you (a) be aware of how closures and <code>var</code> 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.</p> <hr> <p>Consider this example:</p> <pre><code>function ScopeIssues(count) { var funcs = []; for (var i = 0; i &lt; count; ++i) { funcs[i] = function() { console.log(i); } } return funcs; } </code></pre> <p>Short, straightforward...and almost certainly broken. Watch:</p> <pre><code>x = ScopeIssues(100); x[0](); // outputs 100 x[1](); // does too x[2](); // same here x[3](); // guess </code></pre> <p>Every function in the array outputs <code>count</code>. What's going on here? You're seeing the effects of combining closures with a misunderstanding of closed-over variables and scope.</p> <p>When the closures are created, they're not using the value of <code>i</code> at the time they were created to determine what to output. They're using the <em>variable</em> <code>i</code>, 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 <em>called</em>. That will be equal to <code>count</code>, the value that caused the loop to stop.</p> <p>In order to fix this, you'll need another closure.</p> <pre><code>function Corrected(count) { var funcs = []; for (var i = 0; i &lt; count; ++i) { (function(which) { funcs[i] = function() { console.log(which); }; })(i); } } x = Corrected(100); x[0](); // outputs 0 x[1](); // outputs 1 x[2](); // outputs 2 x[3](); // outputs 3 </code></pre> <hr> <p>Another example:</p> <pre><code>value = 'global variable'; function A() { var value = 'local variable'; this.value = 'instance variable'; (function() { console.log(this.value); })(); } a = new A(); // outputs 'global variable' </code></pre> <p><code>this</code> and <code>arguments</code> are different; unlike nearly everything else, they are <em>not</em> shared across closure boundaries<sup>?</sup>. Every function call redefines them -- and unless you call the function like</p> <ul> <li><code>obj.func(...)</code>,</li> <li><code>func.call(obj, ...)</code>,</li> <li><code>func.apply(obj, [...])</code>, or</li> <li><code>var obj_func = func.bind(obj); obj_func(...)</code></li> </ul> <p>to specify a <code>this</code>, then you'll get the default value for <code>this</code>: the global object.<sup>^</sup></p> <p>The most common idiom to get around the <code>this</code> issue is to declare a variable and set its value to <code>this</code>. The most common names i've seen are <code>that</code> and <code>self</code>.</p> <pre><code>function A() { var self = this; this.value = 'some value'; (function() { console.log(self.value); })(); } </code></pre> <p>But that makes <code>self</code> a real variable, with all the potential oddness that entails. Fortunately, it's rare to want to change the value of <code>self</code> without redefining the variable...but within a nested function, redefining <code>self</code> of course redefines it for all the functions nested within it as well. And you can't do something like</p> <pre><code>function X() { var self = this; var Y = function() { var outer = self; var self = this; }; } </code></pre> <p>because of <em>hoisting</em>. JavaScript effectively moves all the variable declarations to the top of the function. That makes the above code equivalent to</p> <pre><code>function X() { var self, Y; self = this; Y = function() { var outer, self; outer = self; self = this; }; } </code></pre> <p><code>self</code> is already a local variable before <code>outer = self</code> runs, so <code>outer</code> gets the local value -- which at this point, is <code>undefined</code>. You've just lost your reference to the outer <code>self</code>.</p> <hr> <p><sup>* As of ES7. Previously, there were only two, and variables were even easier to track down. :P</sup></p> <p><sup>? Functions declared using lambda syntax (new to ES7) don't redefine <code>this</code> and <code>arguments</code>. Which potentially complicates the matter even more.</sup></p> <p><sup>^ 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, <code>this</code> defaults to <code>undefined</code> rather than the global object. But it's still some whole other value than you usually intended to mess with.</sup></p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload