DEV Community

Cover image for Javascript — Lexical and Dynamic Scoping?
Osman Akar
Osman Akar

Posted on

Javascript — Lexical and Dynamic Scoping?

What is Scope?

Scoping itself is how you search for a variable with a given name. A variable has a scope which is the whole area in which that variable can be accessed by name.

In Javascript, we can call Scope as the set of rules that govern how the Engine can look up a variable by its identifier name and find it.

There are two types of Scope models that are widely used. By far the most commonly used Scope model by vast majority of programming languages is Lexical Scope, also Javascript uses this Lexical Scope model. The other model which is still used by some languages like Bash scripting is called Dynamic Scope. Now, we will discuss what are these Scope models? Then we will understand the differences between them.


Dynamic Scope

In dynamic scoping, you search in the local function first, then you search in the function that called the local function, then you search in the function that called that function, and so on, up the call-stack.


What is call-stack?

Call-stack is a mechanism for an interpreter to keep track of its place in a script that calls multiple functions — what function is currently being run and what functions are called from within that function, etc. Stack data structure is LIFO that means, last in first out. Let’s understand with an example from MDN:

function greeting() {
   // [1] Some codes here
   sayHi();
   // [2] Some codes here
}

function sayHi() {
   return "Hi!";
}

// Invoke the `greeting` function
greeting();

// [3] Some codes here
Enter fullscreen mode Exit fullscreen mode
  1. Ignore all functions, until it reaches the greeting() function invocation.
  2. Add the greeting() function to the call stack list.
  3. Execute all lines of code inside greeting() function.
  4. Get to the sayHi() function invocation.
  5. Add the sayHi() function to the call-stack list.
  6. Execute all lines of code inside the sayHi() function, until reaches its end.
  7. Return execution to the line that invoked sayHi() and continue executing the rest of the greeting() function.
  8. Delete the sayHi() function from our call-stack list.
  9. When everything inside the greeting() function has been executed, return to its invoking line to continue executing the rest of the Javascript code.
  10. Delete the greeting() function from call-stack list.

That’s all summary of how the call-stack behaves and what it does.


I should mention that dynamic scope actually is a near cousin to another mechanism called this in Javascript. We can understand by the name of the dynamic scope is that scope can be determined dynamically at run-time, rather than statically at author-time.


What is author-time?
This is the time that when developer writes the code. This can be taken to mean, perception of program as you see it before compilation.


Dynamic scope does not concern itself with how and where functions and scopes are declared, but rather where they are called from. That means that, the scope chain is based on call-stack, not the nesting of scopes in code.

Let’s understand dynamic scope by an example from Kyle Simpson’s book — You don’t know JS, Scope&Closure:

Assume that, if Javascript had dynamic scope, when foo() is executed, theoretically the code below would result 3 as the output, but how?

function foo() {
    console.log( a ); // 3  (not 2!)
}

function bar() {
    var a = 3;
    foo();
}

var a = 2;

bar();
Enter fullscreen mode Exit fullscreen mode

When foo() cannot resolve the variable reference for a, instead of stepping up the nested (lexical scope — we will mention soon) scope chain, it walks up the call-stack, to find where foo() was called from. Since foo() was called from bar() it checks the variables in scope for bar(), and finds an a there with value 3.

To be clear, Javascript does not have dynamic scope. It has lexical scope. But note that this mechanism is kind of like dynamic scope.

Let’s dive into lexical scope.


Lexical Scope

Lexical scope is a scope that is defined at lexing time. In other words, lexical scope is based on where variables and blocks of scope are authored, by you, at write-time, and thus is set in stone by the time the lexer processes your code.

It is also called as Static Scope. In a lexically scoped language, the scope of an identifier is fixed to some region in the source code containing the identifier’s declaration. This means that an identifier is only accessible within that region.

PS: There is a couple of ways to cheat lexical scope like with ‘with’ and ‘eval()’. But these are not suggested and should not be used in your code anymore. This part will be skipped here and will not be mentioned. You can find more information about this with Kyle Simpson’s book — You don’t know JS, Scope&Closures.

Let’s explain how the lexical scope works with an example from Kyle Simpson’s book:

function foo(a) {

    var b = a * 2;

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

    bar(b * 3);
}

foo( 2 ); // 2 4 12
Enter fullscreen mode Exit fullscreen mode

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.

Kyle Simpson’s book — You don’t know JS, Scope&Closures<br>

  • 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, b.
  • Bubble 3 encompasses the scope of bar, and it includes just one identifier: c.

Scope bubbles are defined by where the blocks of scope are written, which one is nested inside of the other etc. Notice that these nested bubbles are strictly nested.

How the Engine Look-ups?

In the above code snippet, the Engine executes the console.log(…) statement and goes looking for the three referenced variables a, b, and c. It starts with innermost scope bubble, Bubble 3. It won’t find a there, so it goes up one level, out to the next nearest scope bubble, Bubble 2. It finds a there, and so it uses that a. Same thing for b. But c, it does find inside of bar(…), Bubble 3.

The variable c is inside of bar(…) and inside of foo(…), the console.log(…) statement would have found and used the one in bar(…), never getting to the one in foo(…).

Scope look-up stops once it finds the first match. The same identifier name can be specified at multiple layers of nested scope, which is called shadowing (the inner identifier shadows the outer identifier). Scope look-up always start at the innermost scope being executed at the time, and works its way outward/upward until first match and stops.

No matter where a function is invoked from, or even how it is invoked, its lexical scope is only defined by where the function was declared.


The Key Contrast Between Lexical and Dynamic Scoping: Lexical scope is write-time, whereas dynamic scope is run-time. Lexical scope care where a function was declared, but dynamic scope cares where a function was called from.


References


Oldest comments (0)