🏢 Lexical Scope in JavaScript

August 13, 2019

Table of content:

What is lexical scope

The scope is the accessibility of objects (variables/functions) in some part of your program. There’re two types of scope: lexical and dynamic. JavaScripts uses the first one. Lexical scope is a scope that defined during the lexing time (surprisingly).

Function as scope

In javascript each functions defines a separate scope, for example:

function bar(a) {
  var b = 2
  console.log(a, b) // 1, 2
}

bar(1)

When we call the log method to output variable, engine triggers look-up process for a and b, and as far as they are enclosed by bar function they’ll be found in that inner scope.

But, what if variable is absent in function defenition:

function foo(a) {
  var b = 2

  function bar(a) {
    console.log(a, b) // 1, 2
  }

  bar(a)
}

foo(1)

In that case, the look-up process will use an inner (hidden) function property [[Scope]], that refers to parent scope until it goes to the global scope.

Note: look-up process works until it find the first match:

var a = 3
var b = 4

function foo(a) {
  var b = 2

  function bar(a) {
    console.log(a, b) // 1, 2
  }

  bar(a)
}

foo(1)

IIFE

If you want just to enclose data into some scope and you actually don’t need an extra function that has to call after it definition, you can use Immediately Invoked Function Expression:

var a = 3;
(function () {
  var a = 5
  console.log(a) // 5
})()
console.log(a) // 3

Block as scope

In most programming languages scope are defining by blocks of code, javascript is different:

var flag = true
if (flag) {
  var a = 4
  console.log(a) // 4
}
console.log(a) // 4

The reason is in a process called hoisting, we’re going to examine it in the next article.

But with ES6 let/const you can achieve exactly that behavior:

let a = 5
{
  const a = 3
  console.log(a) // 3
}
console.log(a) // 5

When the look-up process is failed it throws an error:

var flag = true
if (flag) {
  let a = 4
  console.log(a) // 4
}
console.log(a) // Uncaught ReferenceError: a is not defined

Loops

let variables have a very useful property - rebinding to each iteration of the loop. Look at the plain loop:

for (let i = 0; i < 10; i++) {
  console.log(i)
}

In the context of rebinding it can be presented like:

{
  let j
  for (j = 0; j < 10; j++) {
    let i = j // re-bound
    console.log(i)
  }
}

try/catch as scope

ES3 supports block-scope for the catch parameter:

try {
  const a = 5
  a++
}
catch (err) {
  var foo = true
  console.log(err) // works
}

console.log(foo) // true
console.log(err) // Uncaught ReferenceError

How to change a lexical scope in runtime

Firstly I have to notice, that it’s a really bad idea, that disables engine optimization of your program.

There’re a few ways to change a lexical scope in runtime:

  • eval:

    • takes a string of code and perform it;
    • modifies the existing lexical scope;
    • with strict-mode uses inner lexical scope.
  • new Function:

    • creates a function using string as a function body.
  • with:

    • creates a new lexical scope;
    • deprecated!