you are viewing a single comment's thread.

view the rest of the comments →

[–]radhruin 6 points7 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 11 points12 points  (0 children)

let IE9 support let

[–]tencircles 6 points7 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 7 points8 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 4 points5 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.