Table of contents

  • What is Scope
  • Global Scope
  • Local Scope
  • Function scope
  • Lexical Scope
  • Scope Chain
  • Closures

What is Scope?

Scope is the accessibility of variables, functions, and objects in some particular part of your code during runtime. In other words, scope determines the visibility of variables and other resources in areas of your code.


In the JavaScript language there are two types of scopes:

  • Global Scope
  • Local Scope

Variables defined inside a function are in local scope while variables defined outside of a function are in the global scope. Each function when invoked creates a new scope.

Global Scope

When you start writing JavaScript in a document, you are already in the Global scope. There is only one Global scope throughout a JavaScript document. A variable is in the Global scope if it’s defined outside of a function.

// the scope is by default global
var name = 'Hammad';

Variables inside the Global scope can be accessed and altered in any other scope.

var name = 'Hammad';

console.log(name); // logs 'Hammad'

function logName() {
    console.log(name); // 'name' is accessible here and everywhere else
}

logName(); // logs 'Hammad'

Local scope

Variables defined inside a function are in the local scope. And they have a different scope for every call of that function. This means that variables having the same name can be used in different functions. This is because those variables are bound to their respective functions, each having different scopes, and are not accessible in other functions.

Capture.PNG

Just as a block or function is nested inside another block or function, scopes are nested inside other scopes. So, if a variable cannot be found in the immediate scope, *Engine* consults the next outer containing scope, continuing until found or until the outermost (aka, global) scope has been reached.

function foo(a) {
  console.log( a + b );
}
var b = 2;
foo ( 2 );  // 4

The RHS reference for `b` cannot be resolved inside the function `foo`, but it can be resolved in the *Scope* surrounding it (in this case, the global).

fig1.png

The building represents our program’s nested *Scope* rule set. The first floor of the building represents your currently executing *Scope*, wherever you are. The top level of the building is the global *Scope*.

function foo(a) {
  console.log( a + b );   // ReferenceError
  b = a;
}
foo ( 2 );

When the RHS look-up occurs for b the first time, it will not be found. This is said to be an undeclared “variable, because it is not found in the scope.

If an RHS look-up fails to ever find a variable, anywhere in the nested Scope, this results in a ReferenceError being thrown by the Engine. It’s important to note that the error is of the type ReferenceError.

By contrast, if the Engine is performing an LHS look-up and arrives at the top floor (global Scope) without finding it, and if the program is not running in Strict Mode, then the global Scope will create a new variable of that name **in the global scope**, and hand it back to *Engine*.

*”No, there wasn’t one before, but I was helpful and created one for you.”*

Strict Mode“, which was added in ES5, has a number of different behaviors from normal/relaxed/lazy mode. One such behavior is that it disallows the automatic/implicit global variable creation. In that case, there would be no global Scope variable to hand back from an LHS look-up, and Engine would throw a `ReferenceError` similarly to the RHS case.

Now, if a variable is found for an RHS look-up, but you try to do something with its value that is impossible, such as trying to execute-as-function a non-function value, or reference a property on a null or undefined value, then *Engine* throws a different kind of error, called a TypeError.

ReferenceError is Scope resolution-failure related, whereas TypeError implies that Scope resolution was successful, but that there was an illegal/impossible action attempted against the result.


Review :

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, which is an LHS (left-hand-side) reference, or it may be for the purposes of retrieving its value, which is an RHS (right-hand-side) reference.

The JavaScript *Engine* first compiles code before it executes, and in so doing, it splits up statements like `var a = 2;` into two separate steps:

1. First, `var a` to declare it in that *Scope*. This is performed at the beginning, before code execution.

2. Later, `a = 2` to look up the variable (LHS reference) and assign to it if found.

Both LHS and RHS reference look-ups start at the currently executing *Scope*, and if need be (that is, they don’t find what they’re looking for there), they work their way up the nested *Scope*, one scope (floor) at a time, looking for the identifier, until they get to the global (top floor) and stop, and either find it, or don’t.

Unfulfilled RHS references result in `ReferenceError`s being thrown. Unfulfilled LHS references result in an automatic, implicitly-created global of that name (if not in “Strict Mode” , or a `ReferenceError` (if in “Strict Mode”).


fig2.png

There are three nested scopes inherent in this code example. It may be helpful to think about these scopes as bubbles inside of each other.

Bubble 1 encompasses the global scope and has just one identifier in it: foo.

Bubble 2 encompasses the scope of foo, which includes the three identifiers: ‘a’, bar and `b`.

Bubble 3 encompasses the scope of ‘bar’, and it includes just one identifier: `c`.

what exactly makes a new bubble? Is it only the function? Can other structures in JavaScript create bubbles of scope?

each function you declare creates a bubble for itself, but no other structures create their own scope bubbles. this is not quite true.

The traditional way of thinking about functions is that you declare a function, and then add code inside it. But the inverse thinking is equally powerful and useful: take any arbitrary section of code you’ve written, and wrap a function declaration around it, which in effect “hides” the code.

function doSomething(a) {
   b = a + doSomethingElse( a * 2 );
   console.log( b * 3 );
}
function doSomethingElse(a) {
   return a - 1;
}
var b;
doSomething( 2 ); // 15

In this snippet, the ‘b’ variable and the doSomethingElse(..) function are likely “private” details of how doSomething(..) does its job. Giving the enclosing scope “access” to b and doSomethingElse(..) is not only unnecessary but also possibly “dangerous”, in that they may be used in unexpected ways, intentionally or not, and this may violate pre-condition assumptions of doSomething(..).

A more “proper” design would hide these private details inside the scope of doSomething(..), such as:

function doSomething(a) {
   function doSomethingElse(a) {
      return a - 1;
   }
 var b;
 b = a + doSomethingElse( a * 2 );
 console.log( b * 3 );
}
doSomething( 2 ); // 15

Now, b and doSomethingElse(..) are not accessible to any outside influence, instead controlled only by doSomething(..). The functionality and end-result has not been affected, but the design keeps private details private, which is usually considered better software.


Block Scope

A programming language has block scope if a variable declared inside some block of code enclosed by curly braces is only visible within that block of code, and that variable is not visible outside of that particular block of code.

Think of a block of code as if statement, for loop, while loop, etc.

function scopeTest() {
  for (var i = 0; i <= 5; i++) {
    var inFor = i; 
  }
alert(inFor);
}
scopeTest( ); // retrun 5

So Javascript doesn’t use block scope, but it uses function scope.

JS doesn’t have block scope concept but in ECMAScript 6 introduced the let and const keywords. These keywords can be used in place of the var keyword.

Capture.PNG

Global scope lives as long as your application lives. Local Scope lives as long as your functions are called and executed.


Lexical Scope

Whenever you see a function within another function, the inner function has access to the scope in the outer function, this is called Lexical Scope or Closure – also referred to as Static Scope. The easiest way to demonstrate that again:

Capture

Lexical scope is easy to work with, any variables/objects/functions defined in its parent scope, are available in the scope chain. For example:

var name = 'Todd';
var scope1 = function () {
 // name is available here
  var scope2 = function () {
     // name is available here too
  var scope3 = function () {
        // name is also available here!
 };
 };
};

The only important thing to remember is that Lexical scope does not work backwards. Here we can see how Lexical scope doesn’t work.

var name = 'Todd';
var scope1 = function () {
 // name is available here
  var scope2 = function () {
     // name is available here too
  var scope3 = function () {
        // name is also available here!
 };
 };
};

Scope chain

Scope chains establish the scope for a given function. Each function defined has its own nested scope as we know, and any function defined within another function has a local scope which is linked to the outer function – this link is called the chain. It’s always the position in the code that defines the scope. When resolving a variable, JavaScript starts at the innermost scope and searches outwards until it finds the variable/object/function it was looking for.

fig1.png


 var a;
 console.log (a);  // output: undefined
a = 2; 
var a;
console.log (a);  // output: 2
console.log (a);  // output: undefined
var a = 2;

this’s little confused we need to know something about the compilers. Recall that the *Engine* actually will compile your JavaScript code before it interprets it. Part of the compilation phase was to find and associate all declarations with their appropriate scopes. So, the best way to think about things is that all declarations, both variables and functions, are processed first before any part of your code is executed.

When you see var a = 2;, you probably think of that as one statement. But JavaScript actually thinks of it as two statements: var a; and a = 2;. The first statement, the declaration, is processed during the compilation phase. The second statement, the assignment, is left **in place** for the execution phase.

So, one way of thinking, sort of metaphorically, about this process, is that variable and function declarations are “moved” from where they appear in the flow of the code to the top of the code. This gives rise to the name “Hoisting“.

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

Function declarations are hoisted, as we just saw. But function expressions are not.

foo(); //---- Not ReferenceError, but TypeError!
var foo = function bar() {
 // ...
};

The variable identifier foo is hoisted and attached to the enclosing scope (global) of this program, so foo() doesn’t fail as a ReferenceError. But foo has no value yet (as it would if it had been a true function declaration instead of an expression). So, foo() is attempting to invoke the undefined value, which is a TypeError illegal operation.

foo();    // TypeError
bar();    // ReferenceError
var foo = function bar() {
   // ...
}
var foo;

foo(); // TypeError
bar(); // ReferenceError

foo = function() {
 var bar = ...self...
 // ...
}

functions are hoisted first, and then variables.


Closure

function foo() {
  var a = 2;
    function bar() {     // -- has a closure over the foo's scope
    console.log( a );   // 2
    }
 bar();
}
foo();

 so what’s closure? – From a purely academic perspective, what is said of the above snippet is that the function bar() has a *closure* over the scope of foo() (and indeed, even over the rest of the scopes it has access to, such as the global scope in our case). Put slightly differently, it’s said that bar() closes over the scope of foo(). Why? Because bar() appears nested inside of foo(). Plain and simple.

function sayHello2(name) {
  var text = 'Hello ' + name; // Local variable
  var say = function() { console.log(text); }
  return say;
}
var say2 = sayHello2('Bob');
say2(); // logs "Hello Bob"

The above code has a closure because the anonymous function function() { console.log(text); } is declared inside another function, sayHello2() in this example. In JavaScript, if you use the function keyword inside another function, you are creating a closure

In C and most other common languages, after a function returns, all the local variables are no longer accessible because the stack-frame is destroyed.

In JavaScript, if you declare a function within another function, then the local variables can remain accessible after returning from the function you called. This is demonstrated above because we call the function say2() after we have returned from sayHello2(). Notice that the code that we call references the variable text, which was a local variable of the function sayHello2().

function() { console.log(text); } // Output of say2.toString();

Looking at the output of say2.toString(), we can see that the code refers to the variable text. The anonymous function can reference text which holds the value 'Hello Bob' because the local variables of sayHello2() are kept in a closure.

function greeter(name, age) {
  var message = name + ", who is " + age + " years old, says hi!";

  return function greet() {
    console.log(message);
  };
}

// Generate the closure
var bobGreeter = greeter("Bob", 47);
// Use the closure
bobGreeter();

Note that the greet function is nested within the greeter function. This means it’s within the lexical scope of greeterand thus according to the rules of closure has access to the local variables of greeter including message, name, and age.

function Person (name){
 var name = name; // this is a private variable
 this.getName = function (){
 return name; 
 };
}
var p = new Person("Ahmed");
console.log(p);

Capture.PNG

another example

var add = (function (){
 var counter = 0;
 return function (){
 console.log( counter++);
 };
})();

add();  // 0
add();  // 1
add();  // 2
add();  // 3

The variable add is assigned the return value of a self-invoking function.

  • The self-invoking function only runs once. It sets the counter to zero (0) and returns a function expression.
  • This way add becomes a function. The “wonderful” part is that it can access the counter in the parent scope.
  • This is called a JavaScript closure. It makes it possible for a function to have “private” variables.
  • The counter is protected by the scope of the anonymous function, and can only be changed using the add function.

A closure is a function having access to the parent scope, even after the parent function has closed.


Resources

Advertisements