On JavaScript variable resolution and lexical scoping

This is supposed to be crystal clear. And it is, once you fully understand the internal mechanisms.

So the problem, demonstrated using an event handler example, is this:

[code lang=”js”] var foo = ‘bar’; var numberThatCanChange = 0;

function myEventHandler(e) { console.log(‘Handling event…’); console.log(foo, numberThatCanChange); }

$(‘.my-div’).on(‘click’, myEventHandler);

numberThatCanChange = 1;

// now click happened here [/code]

This is very common pattern we see all the time. It’s obvious, from experience that the console.log won’t be invoked until event is emitted. That is, when interpreter goes through the lines defining a function, it will only parse but not executing the lines.

But the real question is, if myEventHandler is taken as a callback, and executed somewhere else, will it still have access to variables that were outside, but were available? If yes, why there is access, and is those variable’s value as manifested in callback subject to any subsequent changes?

Answer is here:

[code] Handling event… bar 1 [/code]

So, it does have access to outer variables, and changes to those variables will reflect. Why?

In JavaScript, prior to ES6, there is only function scope. This is in contrast to, say Java, where there is block scope. First at the outside there is the global scope. Then when there is a function definition or function expression, a lexical scope is created inside the function body. Any variables defined using var are under its enclosing scope. Named parameters are very similar. When invoked like this:

[js] var argObj = { foo: ‘bar’ }; function myFunc(arg) { // } myFunc(1); myFunc(argObj) [/js]

It is equivalent in effect to:


function myFunc() { var arg = { foo: ‘bar’ }; } // or function myFunc() { var arg = 1; } [/js]

Now, a function scope is able to access the variables in the immediate outer enclosing scope, and the next outer scope and so on. This is because each lexical scope has link to it’s enclosing scope, and will try to find the variable if not found on current scope. The resolution goes on, until global scope. This is similar to prototype resolution, and shadowing applies. This is why, sometimes JS code would establish a local variable pointing to a outer variable, to gain performance benefit.

With this knowledge, we have a deeper understanding of the common idiom, where we use closure to “capture” value of index variable:

[js] var i; for (i=0; i<arr.length; i++) { !function(i) { $(‘.my-div’).on(‘click’, function(e) { console.log(i); }); }(i); } [/js]

Why this is able to print 1 2 3... not 3 3 3? Because every iteration, inside the loop a new scope is created. That scope from anonymous function has a local variable i, and its value is assigned to 1, 2, 3… When event handler is invoked, it first find the immediate enclosing scope, which has a variable called i. So it uses that. Compare this with no closure, where the event handler will go to the outer scope for i, and find the already-incremented-to-length i repeatedly, thus printing 3 3 3.

On ES6 block scope is introduced. These links give good introduction to that: let - JavaScript | MDN The Joys of Block Scoping with ES6

Written on March 22, 2016