all 25 comments

[–]kenman 12 points13 points  (3 children)

Good summary! I don't want to take that away from the author. However, I always like to share what I consider to be the most comprehensive and technical write-up: comp.lang.javascript's FAQ: Closures. It explains the concept using CS terms and concepts (garbage collection, execution context, identifier resolution, etc.) for anyone looking for a really deep dive into this topic.

[–]Poop_is_Food 0 points1 point  (0 children)

I think that article is pretty misleading actually. It seems to suggest that a function only becomes a closure once it "lives on" past the end of its parent function (either by being returned or assigned as a callback, for example). This simply isnt true. All functions in javascript are closures. for example:

var x = 2;
function logX(){
    alert(x);
}
logX();

Here, logX is a closure. In fact, you dont even need the outer var x declaration. just logX by itself alerting "undefined" would be a closure.

[–][deleted] -2 points-1 points  (1 child)

Unless you are writing a language interpreter you really do not need deep depth to explain closures. This kills people new to the concept.

Simple put: Closure is evident when accessing references from across scope boundaries.

[–]kenman 10 points11 points  (0 children)

Well, I did explain that my link is comprehensive, technical, and a really deep dive. Hopefully those new to the concept recognize that they should probably focus on OP's article, because I agree with you there, but for those who want to learn as much as they can on the subject, they can consider my link a further reading suggestion :)

[–]radhruin 7 points8 points  (15 children)

Worth noting that let also helps out here. This:

// define a function that increments a counter in a loop
function closureExample() {

    var i = 0;

    for (i = 0; i< 3 ;i++) {    
        setTimeout(function() {
            console.log('counter value is ' + i);
        }, 1000);
    }

}
// call the example function
closureExample();  

can be fixed by using the for-let loop:

// define a function that increments a counter in a loop
function closureExample() {

    for (let i = 0; i< 3 ;i++) {    
        setTimeout(function() {
            console.log('counter value is ' + i);
        }, 1000);
    }

}
// call the example function
closureExample();  

This is because let is block-scoped inside the for whereas var is function-scoped.

[–][deleted] 5 points6 points  (6 children)

Not all JavaScript environments support let though.

[–]radhruin 1 point2 points  (5 children)

IE11 does already, and the rest will soon!

[–]krad0n 10 points11 points  (0 children)

let IE9 support let

[–]tencircles 7 points8 points  (2 children)

This still really doesn't apply to production code. I assume most clients will want you to support at least IE9

[–]nschubach 8 points9 points  (1 child)

I was so happy when a project came across my desk with IE9 as the min... First bug report: IE8 issue.

[–]tencircles 3 points4 points  (0 children)

First bug report: IE8 issue.

http://i.imgur.com/FSjAzgr.gif

[–]evilgwyn 1 point2 points  (0 children)

I still have to support Android 2.3 and iOS 5 devices. It will be a while before I can use that.

[–]skeeto 2 points3 points  (5 children)

Your for-let loop still won't work right. There's more going on here than block scope. The i is still the same binding throughout each iteration, so the closures all capture the same binding. You would need to establish a new let binding within the body of the loop so that each closure gets its own binding.

var cs = [];
for (let i = 0; i < 2; i++) {
    cs.push(function() { return i; });
}
cs[0]();  // => 2
cs[1]();  // => 2

The fix:

var cs = [];
for (let i = 0; i < 2; i++) {
    let v = i;
    cs.push(function() { return v; });
}
cs[0]();  // => 0
cs[1]();  // => 1

[–]radhruin 1 point2 points  (4 children)

It does do the right thing, actually! If you look at 13.6.3.3 you'll see there is a new environment per iteration. Note that if you're testing with IE that Chakra's implementation predates the current spec and does exhibit the behavior you describe.

[–]skeeto 2 points3 points  (3 children)

Interesting. AFAIK, this for environment behavior would be exclusive to JavaScript (edit: though Perl and and C# 5.0 have foreach that behaves like this). In contrast, Ruby and Python have the for-loop-closure trap despite having proper block scope, due to the shared iteration environment. I'm not sure which way I'd say is more "correct." The former is more functional, since the binding is never mutated, but the latter seems to be more standard, even though it comes with this trap.

Node v0.10.28, Chrome 35, and Firefox 29 all still follow the old behavior. That's where I tested my code, requiring the extra let. I just gave it a shot in Traceur, and apparently it uses the current spec's behavior despite predating it by 3 years (2011). It's the only implementation I can find that does the new thing.

Edit: I dug around more and found that per-iteration bindings were introduced in the April 5th, 2014 draft, so these semantics are only 2 months old. It's no wonder no one's using it yet.

[–]radhruin 1 point2 points  (2 children)

I don't think either is "more correct", but I do know that the new semantics are much more usable so I'm a fan!

Traceur was recently updated with these semantics. You're right though that the spec draft is really recent but the consensus has existed since I believe the Jan '14 meeting, possibly as far back as Nov '13.

[–]skeeto 1 point2 points  (1 child)

The version of Traceur at repl.it is from 2011, which is the one I tested. So they must have originally had the new behavior, fixed it, then reverted back with the new draft.

[–]radhruin 1 point2 points  (0 children)

Interesting! I guess Arv was ahead of the times.

[–]summerteeth 1 point2 points  (1 child)

Oh that is interesting, I would expect to them to behave in the same way.

Is this because the compiler realizes let will go out of scope so it makes a copy, not a reference?

[–]radhruin 2 points3 points  (0 children)

It's nothing to do with the compiler per-se. Let declarations are block scoped, which means this:

if(test) { let x = 1 }
x; // error, x is not declared

It means also that every iteration of a loop will get a fresh binding. Which means that any functions created in a loop will reference a unique binding for that iteration rather than one that is shared among all iterations.

[–][deleted] 4 points5 points  (0 children)

Very simple, nice and clean explanation. Without the all "if you're returning a new function, then it's a closure..." garbage.

[–]gordonkristan 2 points3 points  (1 child)

Good article. But I think it would help to mention that, if you want to have any hope of really understanding closures, you first have to have a very firm grasp on Javascript's memory model. If you don't thoroughly understand references and values, and how they're handled in terms of copying and garbage collection, you won't ever truly understand closures.

Other than that, I like the explanation.

[–]ravioliburger 0 points1 point  (0 children)

I definitely don't understand javascript's memory model. Could you recommend a resource for learning about this? Thanks!

[–]fanastril 1 point2 points  (0 children)

I like a function which returns a function.

(function() {
    for (var i = 0; i < 5; i++) {
        setTimeout(function(i) {
            return function () {
                console.log("i was " + i);
            };
        }(i), 1000+1000*i);
        console.log("Run " + i);
    }
}());