Skip to content
sergio edited this page Nov 9, 2020 · 4 revisions
Sumup

JavaScript exhibits "Function-level" scope. This means that if a function is defined inside another function, the inner function has access to all the parameters (variables passed in) as well as any variables defined in the function. This moves backward recursively too. Each of the enclosing parents' scopes are made available via the scope chain.

function outer(greeting, msg="It's a fine day to learn") { // 2
  let innerFunction =  function(name, lang="Python") { // 3
    return `${greeting}, ${name}! ${msg} ${lang}` // 4
  }
  return innerFunction("student", "JavaScript") // 5
}

outer("Hello") // 1
//=> "Hello, student! It's a fine day to learn JavaScript"

Scope

Scope is the concept of where something is available. In JavaScript, scope is defined by where declared variables and methods are available within our code.

Top Tip: If a variable or function is not declared inside a function or block, it's in the global execution context.

Variables declared with var are not block-scoped:

if (true) {
  var myVar = 42;
}

myVar;
// => 42

However, variables declared with const and let are block-scoped:

if (true) {
  const myVar = 42;

  let myOtherVar = 9001;
}

myVar;
// Uncaught ReferenceError: myVar is not defined

myOtherVar;
// Uncaught ReferenceError: myOtherVar is not defined

This is yet another reason to never use var. As long as you stick to declaring variables with const and let, what happens in block stays in block.

Variables created without a const, let, or var keyword are always globally-scoped, regardless of where they sit in your code.

In general, it's best practice to make variables and functions available only where they're needed — and nowhere else. Making a variable available in places that shouldn't have access to it can only lead to bad things and make your debugging process more complex. The more pieces of code that can access a given variable, the more places you have to check for bugs if/when that variable contains an unexpected value. So keep your scopes small and specific.



Every function in JavaScript has access to a scope chain, which includes references to the function's outer scope (the scope in which the function was declared), the outer scope's outer scope, and so on. the scope chain allows us to access variables and functions declared in outer scopes within an inner function.

When that a function is invoked, it creates a new scope and retains a reference to the outer scope in which it was declared. Inside the new function's body, in addition to variables and functions declared in that function, we also have access to variables and functions declared in the outer scope. When the engine can't find a local match, it then goes looking in the outer scope and finds a match there.

NOTE: The scope chain only goes in one direction. An outer scope does not have access to things declared in an inner scope. two functions declared in the same scope do not have access to anything declared in the other's scope:


JavaScript engine actually makes two separate passes over our code:

Compilation Phase

Engine steps through our code line-by-line:

  • Encounters a Variable
    • Ellocates memory and sets up a reference to the Variable's identifier myVar
  • Encounters a Function
    • Allocates memory and sets up a reference to the Function's identifier myFunc
    • Creates a new execution context with a new scope
    • Adds a reference of the outer environment to the scope chain of myFunc
const myVar = 42;

myVar;
// => 42

During the compilation phase, a reference to the identifier myVar is stored in memory. The variable isn't yet assigned a value, and the second line (myVar;) is skipped over entirely because it isn't a declaration.

Execution Phase

JavaScript engine again steps through our code line-by-line, but this time it actually runs our code, assigning values to variables and invoking functions.

During the execution phase, the value 42 is assigned to myVar. When the engine reaches the second line, it sees the identifier myVar and resolves it to a value through a process known as identifier resolution. The engine first checks the current scope to see if myVar has been declared in it. If it finds no declaration for myVar in the current scope, the engine then starts moving up the scope chain, checking the parent scope and then the parent scope's parent scope and so on, until it finds a matching declared identifier or reaches the global scope. If the engine traverses all the way up to the global scope and still can't find a match, it will throw a ReferenceError and inform you that the identifier is not declared anywhere in the scope chain.

The engine will continue to move up the scope chain only if it can't find a matching identifier in the current scope. Because of this, we can actually use the same identifier to declare variables or functions in multiple scopes:

const myVar = 42;

function myFunc () {
  const myVar = 9001;

  return myVar;
}

myFunc();
// => 9001



how JavaScript decide which outer scope to place into the scope chain for a new function.

const myVar = 'Foo';

function first () {
  console.log('Inside first()');

  console.log('myVar is currently equal to:', myVar);
}

function second () {
  const myVar = 'Bar';

  first();
}

second();
// LOG: Inside first()
// LOG: myVar is currently equal to: Foo
// => undefined

the assignment of myVar as 'Bar' is not visible to first(). This is because second() is not the parent scope of first().

first() is declared in the global scope, and, when it comes to the scope chain, JavaScript functions don't care where they are invoked. The only thing that matters is where they are declared. When we declare a new function, the function asks, "Where was I created?" The answer to that question is the outer environment (the outer scope) that gets stored in the new function's scope chain.

By contrast, if we declare first() inside second(), then first()'s reference to its outer scope points at second() instead of at the global scope:

const myVar = 'Foo';

function second () {
  function first () {
    console.log('Inside first()');

    console.log('myVar is currently equal to:', myVar);
  }

  const myVar = 'Bar';

  first();
}

second();
// LOG: Inside first()
// LOG: myVar is currently equal to: Bar
// => undefined

Lexical scope is the scope defined by author-time decisions of where functions are declared. The lexing phase of compilation knows where and how all identifiers are declared, and determine how they will be looked-up during execution.

Clone this wiki locally