I run into a bit of confusion regarding scoping with respect to where a callback is defined.
function test(){
var b = 3
var fun = function(){
var printingFunction = function(){
console.log(b)
}
printingFunction()
}
return fun
}
test()() //prints 3, as expected because of closures
However, the following doesnt work
function test(){
var b = 3
var fun = function(cb){
cb()
}
return fun
}
test()(function(){console.log(b)}) //b is not defined
I would expect that since the function is passed as an argument and has not been defined before, its definition takes place inside 'fun' and therefore it would have access to b. Instead, it looks a lot like the function is first defined in the scope where its passed and THEN passed as an argument. Any ideas/pointers?
EDIT: Some extra pointers.
someFunction("a")
We couldn't possibly claim that "a" is a definition. What happens here implicitly is that "a" is assigned to a variable named by the argument name so var argumentNameInDefintion = "a"
. This happens in the body of someFunction.
Similarly we cant claim {}
is a definition in : someFunction({})
. So why would:
someFunction(function(){})
decide that function(){}
is a definition is beyond me. Had it been
var a = function(){}
someFunction(a)
everything would make perfect sense. Maybe its just how the language works.
Scoping in JavaScript is lexical. If you look at your examples, you can see that where printingFunction
is defined, lexically (e.g., in the source text) b
is declared in a containing scope. But in your second example, it isn't. That's why b
can't be resolved in your second example but can in your first.
The way it works is that when a function is created, it has a reference to a conceptual object containing the variables and such in the scope in which it's created (which has a fancy name: "Lexical Environment object"); and that object has a reference to the one that contains it. When looking up a variable reference, the JavaScript engine looks at the current lexical environment object and, if it finds the variable, uses it; otherwise, it looks to the previous one in the chain, and so on up to the global one.
More details can be found:
- In this SO question's answers
- In this post on my anemic little blog
The issue stems from me not understanding that function(){}
on its own is a definition and so is "a"
and {}
. Since these are definitions then the definition scope of the function passed is appropriately placed where it is and the world makes sense again.
In first case, it is forming a closure and has access to the variable "b" but in second case it does not form a closure at all. If you put a debugger just before the cb() inside the function, you will notice that there is no closure formed and the reason of that being the callback function is suplied to the function as an argument and it becomes local to that function which does not have any knowledge of the variable b.
Think of it as two different functions, one in which it has a local variable "b" and in other no local variable but we are trying to access it which throws the reference error.