1

Heres a sample bit of code, similar to that that gave me grief:

for(var i=0;i<3;i++){
    var Proof = true
    if (i == 0){
        Proof = false
        //var CallBack = function(){alert(Proof);}; // true????
        var CallBack = function(){alert(i);};
    }
}

// Whatever happened to local scope? shouldn't CallBack be undefined now?
CallBack()// alert(3), should be 0?

CallBack isn't undefined, it alerts true or 3, could someone please explain to me what is going on that is making it act like this? is there some local keyword I am missing, or does JS not have scope? I am using no framework, and would appreciate some help on how to sort this out, and do this kind of thing properly...

Weeve Ferrelaine
  • 585
  • 6
  • 12

2 Answers2

2

JavaScript doesn't have block scope at all (yet, see below). All variables are declared throughout the function in which they appear (or throughout the global scope, if they're at global scope).

Assuming the code you've quoted is the only code in its scope, it's exactly the same as this:

var i;
var Proof;
var Callback;

for(i=0;i<3;i++){
    Proof = true
    if (i == 0){
        Proof = false
        //CallBack = function(){alert(Proof);}; // true????
        CallBack = function(){alert(i);};
    }
}

// Whatever happened to local scope? shouldn't CallBack be undefined now?
CallBack()// alert(3), should be 0?

More (on my blog): Poor, misunderstood var


CallBack()// alert(3), should be 0?

No, 3 is correct, because that's the value of i as of when CallBack is called. CallBack is a closure over the context in which it was created. It has an enduring reference to the context (including the i variable), not a copy of what existed when it was created.

If you wanted to get 0 instead, you'd have to have CallBack close over something other than i, something that won't change. The typical way to do that is to use a builder function you pass a value into:

function buildCallBack(index) {
    return function() { alert(index); };
}

Then:

CallBack = buildCallBack(i);

The value of i is passed into buildCallBack as the argument index. buildCallBack creates a function closing over the context of that call to buildCallBack and using that index argument. Since that argument's value is never changed, the callback alerts 0 (in this case) rather than 3.

More (on my blog): Closures are not complicated


The reason I said "yet" above is that the next version of the spec (ES6) will introduce new block-scope semantics for the new let and const keywords and block function declarations. var will be unchanged, but if you use let to declare a variable instead, it has block scope rather than function/global scope.

T.J. Crowder
  • 879,024
  • 165
  • 1,615
  • 1,639
  • Shouldn't at least pass by copy apply though, for callback's stack? or is pass by copy only for direct variable assignments, and its also kinda scary that I have written more than a comfortable amount of Js code already without understanding this – Weeve Ferrelaine Jan 08 '14 at 08:49
  • @JohnDoe: What pass by copy? The question seems to be about scope, not function calls. – T.J. Crowder Jan 08 '14 at 08:50
  • Sorry, in Lua (my native language) variables carried over in the function body of a local function are declared in its own scope (and every scope has a 'copy' of the variables when it reaches is) so even though the function would be called later, the function's 'copy' of the variable would still be active – Weeve Ferrelaine Jan 08 '14 at 08:54
  • @JohnDoe: I see. I just noticed the comment in the code asking why the alert shows 3 rather than 0, so I added to the answer to address that. – T.J. Crowder Jan 08 '14 at 08:56
  • Thank-you for explaining the lack of scope :-) although Zub's answer seems to be more appealing, because of my application, I'm weighing more for syntax cleanliness, than for speed. – Weeve Ferrelaine Jan 08 '14 at 09:16
  • @JohnDoe: Well, cleanliness is a subjective thing, it's up to you. There are objective reasons not to use the anonymous function approach if you were doing more than one, but as you're only creating the one (in your case), it's fine. I always prefer the separate builder for clarity and (in my view) cleanliness. – T.J. Crowder Jan 08 '14 at 09:27
  • When I'm alive again in the morning, I'll put the finishing decisions on what to do, been awake long enough narrowing down why my code is doing this, to make a suitable post to StackOverflow. – Weeve Ferrelaine Jan 08 '14 at 09:30
1

When CallBack is being called, for loop is finished already. So i equals 3.

If you want i to be local, you should write like this:

var CallBack;
for(var i=0;i<3;i++){
    (function(index) {
        var Proof = true
        if (index == 0){
            Proof = false
            //var CallBack = function(){alert(Proof);}; // true????
            CallBack = function(){alert(index);};
        }
    })(i);
}

CallBack()

Here is a demo

UPDATE

Here is an alternative:

var CallBack;
for(var i=0;i<3;i++){
    var Proof = true
    if (i == 0){
        Proof = false
        CallBack = (function(index) {
            return function(){alert(index);};
        })(i);
    }
}

CallBack()

demo

UPDATE 2

EXPLAINATION:

By writing (function(index) {...})(i) we declare an anonymous function and call it immediately (we could also write function(index) {...}(i) but I think the first case is clearer). In javascript, function is also a way to set another scope of variables, so in our case we "remember" the current state of i variable in index variable that is visible only within our anonymous function.

And when the 'CallBack' is called, alert shows 0 because i was equal 0 when that function was called.

Oleg
  • 19,312
  • 9
  • 59
  • 80
  • 1
    It's generally better to have the builder for the callback be a separate, named, reusable function, **particularly** in the case where you're looping. Otherwise, in addition to clarity issues, you're creating and throwing away the builder function repeatedly for no good reason. – T.J. Crowder Jan 08 '14 at 08:58
  • Interesting idea to hack it with threads, But why encase the function in ( )? and how are you passing i to it in what looks like a call, but seems not to be '(function{})(i)'? – Weeve Ferrelaine Jan 08 '14 at 08:59
  • @John Doe by writing `(function(i) {...})(i)` I declare function and call it immidiately. I pass `i` and `i` variable appears in a function body (I could name it differently). That inner `i` reflects current outer `i` state – Oleg Jan 08 '14 at 09:02
  • @T.J. Crowder to use a builder is a good idea. I didn't think about it – Oleg Jan 08 '14 at 09:05
  • Sorry, my mind is jumping around a lot, 3:00 AM here, didn't mean to say thread, but it seems irrelevant now, so in JS, why wouldn't 'function(){}()' be valid, for your script, or just attempting at cleaner syntax? and him using an anonymous function to make an anonymous functions seems pretty clear to me, perhaps not to future viewers, if there are any – Weeve Ferrelaine Jan 08 '14 at 09:11
  • @John Doe, `function(){}()` is valid too. I encase function into `()` just for clarity – Oleg Jan 08 '14 at 09:13
  • Alright, thank-you for posting a workaround thats compact, I'll find some way to hack this in to look pretty for my project, and not sure why, I may be alien, but the function(){}() seems more clear to me than (function(){})(). – Weeve Ferrelaine Jan 08 '14 at 09:19
  • @John Doe, by using `function(){}()` you might not pay attention at ending `()` if the function is large. So it may be confusing. But do what you think is right :) – Oleg Jan 08 '14 at 09:26
  • 1
    @JohnDoe: *"...the function(){}() seems more clear to me than..."* As long as you're already in an expression (and in your case you are, you're on the right-hand side of an `=`), you can do that, no problem. There are times you need those outer parens to avoid the parser treating the expression like a function *declaration*; [details here](http://stackoverflow.com/questions/13341698/javascript-plus-sign-in-front-of-function-name/13341710#13341710). – T.J. Crowder Jan 08 '14 at 09:29