felfel.dev

Understanding Scopes in javascript

December 23, 2015 • ☕️ 6 min read

There’re two types of scope in javascript, lexical and dynamic scopes, and in this article we will give a brief intro to how each one works, their issues, hacks, and how ES6 (aka 2015) solved a lot of those issues.

What is a scope ?

As Kyle Simpson describes it in his book “You don’t know javascript: scopes and closures” :

Scope is the set of rules that determines where and how a variable (identifier) can be looked-up. This look-up may be for the purposes of assigning to the variable, , or it may be for the purposes of retrieving its value.

1- Lexical Scopes :

Lexical scope looks-up variables in a nested way, the very top one is global scope, then each new function (before es2015 there was no block scope in javascript (without hacks!) so it must be a function) has its own scope, and if it has another function inside it, its scope will be nested from the wrapper function, and so on. e.g.

function foo(a) {

    var b = a * 2;

    function bar(c) {
        console.log( a, b, c );
    }

    bar(b * 3);
}

foo( 2 ); // 2 4 12

In the above example we have 3 scopes, function bar, function foo, and the global scope as follow: scopes 1 source

So when the compiler wants to look-up the value of variable b for example it will start with its own function scope which is function bar’s scope, but there’s no b there, so it will go up to the wrapping scope which is function foo’s scope, where it can find var b declaration.

Hint: you might have noticed that it is considered a bad practice to define variables in javascript without using the keyword var because what will happen actually is according to lexical scope’s rules, it will keep going up searching for the variable declaration till it reaches the global scope where javascript will do a weird thing which instead of raising some error, it will define a variable for you!

2- Dynamic Scopes:

Here we will talk mainly about this keyword in javascript, because it is the only built-in object in javascript that represents dynamic scoping.

Unlike lexical scopes, to look up this in javascript there is no one rule of doing it, but it depends on how you wrote your code and invoked your functions.

Just to confirm it doesn’t work the same as lexical scopes let’s take this example:

function foo() { 
    var a = 2; 
    bar(); 
} 
function bar() { 
    console.log( this.a ); 
}
foo(); //ReferenceError: a is not defined

And here is a summary of the different ways of this binding in javascript: 

1- default binding:

This rule comes from the most common case of function calls: standalone function invocation.

function foo() { 
    console.log( this.a ); 
} 
var a = 2; 
foo(); // 2

In this case this will be bound to the global object. Note that If strict mode is in effect, the global object is not eligible for the default binding, so the this is instead set to undefined:

function foo() {
 "use strict"; 
    console.log( this.a ); 
} 
var a = 2; 
foo(); // TypeError: `this` is `undefined`

2- Implicit Binding:

This case happens when we call a method in an object, this will refer to the object that contains this method.

function foo() { 
    console.log( this.a ); 
} 
var obj = {
     a: 2, 
     foo: foo 
};
obj.foo(); // 2

But, what if we want to force a function call to use a particular object for the this binding, without putting a property function reference on the object? and we always see that with different libraries like jQuery:

$('#someId').on('click', function(){
    console.log(this) // <div id="someId" ></div> 
})

as we see, this force-bound to the element that has id="someId".

Those libraries and indeed many new built-in functions in the JavaScript language and host environment, provide an optional parameter, usually called “context” inside this “context” the this keyword refer to specific parameter. these functions use Explicit Binding to make that happen.

3- Explicit Binding:

All functions in the language have some utilities available to them (via their [[Prototype]]), which can be useful for this task. The vast majority of functions, and certainly all functions you will create, do have access to call(..) and apply(..).

function foo() {
    console.log( this.a ); 
} 
var obj = { a: 2 }; 
foo.call( obj ); // 2

Invoking foo with explicit binding by foo.call(..) allows us to force its this to be obj.

4- new Binding:

When a function is invoked with new in front of it, otherwise known as a constructor call, the following things are done automatically:

  1. A brand new object is created (aka constructed) out of thin air. 
  2. The newly constructed object is [[Prototype]]-linked. 
  3. The newly constructed object is set as the this binding for that function call. 
  4. Unless the function returns its own alternate object, the new invoked function call will automatically return the newly constructed object.
function foo(a) {
    this.a = a; 
} 
var bar = new foo( 2 ); 
console.log( bar.a ); // 2

By calling foo(..) with new in front of it, we’ve constructed a new object and set that new object as the this for the call of foo(..).

Lexical this:

Pre-ES6, we already have a fairly common pattern for doing so:

function foo() { 
    var self = this; // lexical capture of `this` 
    setTimeout( function(){
        console.log( self.a ); 
    }, 100 ); 
} 
var obj = { a: 2 }; 
foo.call( obj ); // 2

But ES6 introduced a special kind of function that does not use this binding standard rules, but instead it looks up this as a lexical scope: the arrow-function.

function foo() {
 // return an arrow function 
    return (a) => { 
    // `this` here is lexically inherited from `foo()` 
    console.log( this.a ); 
    }; 
} 
var obj1 = { a: 2 }; 
var obj2 = { a: 3 }; 
var bar = foo.call( obj1 ); 
bar.call( obj2 ); // 2, not 3!

Block Scopes:

pre-ES6, javascript didn’t have a direct way to force variable declarations to be block scoped, the only way to create a lexical scope is to use a function.

function foo() {
   function bar(a) {
       i = 3;
       console.log( a + i );
   }
   
   for (var i=0; i<10; i++) {
       bar( i * 2 );
   }
}
foo(); // WARNING! Infinite loop ahead

When you read the above example for the first time, you don’t expect it to output infinite loop, but because for loop in javascript doesn’t have its own scope, the variable i belongs to the nearest lexical scope which is function foo , and once we call function bar, i will get overwritten to 3 forever!

So to fix these situations, and avoid any unexpected output of our code, ES6 introduced a new way of declaring variables, the two keywords let and const , and when we declare a variable with these new keywords, it will be block-scoped. Let’s now try the same example with let instead of var and see the result.

function foo() {
   function bar(a) {
       i = 3;
       console.log( a + i );
   }
   
   for (let i=0; i<10; i++) {
       bar( i * 2 );
   }
}
foo();

Now with let i inside the for loop, its lexical scope is the nearest block which is the for loop and not the function foo, so the variable collide which causes the i inside bar to overwrite the one inside foo won’t happen and we will get the expected output.


That was a brief overview on Scopes in javascript, if you want to read more here’re some resources:
You Don’t Know JS: this & Object Prototypes
You Don’t Know JS: Scope & Closures
Variables and Scoping - Exploring js
JavaScript’s Apply, Call, and Bind Methods
Understand JavaScript’s “this” With Clarity
Function.prototype.bind() - Mozilla

Thanks!