[javascript] What is the scope of variables in JavaScript?

TLDR

JavaScript has lexical (also called static) scoping and closures. This means you can tell the scope of an identifier by looking at the source code.

The four scopes are:

  1. Global - visible by everything
  2. Function - visible within a function (and its sub-functions and blocks)
  3. Block - visible within a block (and its sub-blocks)
  4. Module - visible within a module

Outside of the special cases of global and module scope, variables are declared using var (function scope), let (block scope), and const (block scope). Most other forms of identifier declaration have block scope in strict mode.

Overview

Scope is the region of the codebase over which an identifier is valid.

A lexical environment is a mapping between identifier names and the values associated with them.

Scope is formed of a linked nesting of lexical environments, with each level in the nesting corresponding to a lexical environment of an ancestor execution context.

These linked lexical environments form a scope "chain". Identifier resolution is the process of searching along this chain for a matching identifier.

Identifier resolution only occurs in one direction: outwards. In this way, outer lexical environments cannot "see" into inner lexical environments.

There are three pertinent factors in deciding the scope of an identifier in JavaScript:

  1. How an identifier was declared
  2. Where an identifier was declared
  3. Whether you are in strict mode or non-strict mode

Some of the ways identifiers can be declared:

  1. var, let and const
  2. Function parameters
  3. Catch block parameter
  4. Function declarations
  5. Named function expressions
  6. Implicitly defined properties on the global object (i.e., missing out var in non-strict mode)
  7. import statements
  8. eval

Some of the locations identifiers can be declared:

  1. Global context
  2. Function body
  3. Ordinary block
  4. The top of a control structure (e.g., loop, if, while, etc.)
  5. Control structure body
  6. Modules

Declaration Styles

var

Identifiers declared using var have function scope, apart from when they are declared directly in the global context, in which case they are added as properties on the global object and have global scope. There are separate rules for their use in eval functions.

let and const

Identifiers declared using let and const have block scope, apart from when they are declared directly in the global context, in which case they have global scope.

Note: let, const and var are all hoisted. This means that their logical position of definition is the top of their enclosing scope (block or function). However, variables declared using let and const cannot be read or assigned to until control has passed the point of declaration in the source code. The interim period is known as the temporal dead zone.

_x000D_
_x000D_
function f() {
    function g() {
        console.log(x)
    }
    let x = 1
    g()
}
f() // 1 because x is hoisted even though declared with `let`!
_x000D_
_x000D_
_x000D_

Function parameter names

Function parameter names are scoped to the function body. Note that there is a slight complexity to this. Functions declared as default arguments close over the parameter list, and not the body of the function.

Function declarations

Function declarations have block scope in strict mode and function scope in non-strict mode. Note: non-strict mode is a complicated set of emergent rules based on the quirky historical implementations of different browsers.

Named function expressions

Named function expressions are scoped to themselves (e.g., for the purpose of recursion).

Implicitly defined properties on the global object

In non-strict mode, implicitly defined properties on the global object have global scope, because the global object sits at the top of the scope chain. In strict mode, these are not permitted.

eval

In eval strings, variables declared using var will be placed in the current scope, or, if eval is used indirectly, as properties on the global object.

Examples

The following will throw a ReferenceError because the namesx, y, and z have no meaning outside of the function f.

_x000D_
_x000D_
function f() {
    var x = 1
    let y = 1
    const z = 1
}
console.log(typeof x) // undefined (because var has function scope!)
console.log(typeof y) // undefined (because the body of the function is a block)
console.log(typeof z) // undefined (because the body of the function is a block)
_x000D_
_x000D_
_x000D_

The following will throw a ReferenceError for y and z, but not for x, because the visibility of x is not constrained by the block. Blocks that define the bodies of control structures like if, for, and while, behave similarly.

_x000D_
_x000D_
{
    var x = 1
    let y = 1
    const z = 1
}
console.log(x) // 1
console.log(typeof y) // undefined because `y` has block scope
console.log(typeof z) // undefined because `z` has block scope
_x000D_
_x000D_
_x000D_

In the following, x is visible outside of the loop because var has function scope:

_x000D_
_x000D_
for(var x = 0; x < 5; ++x) {}
console.log(x) // 5 (note this is outside the loop!)
_x000D_
_x000D_
_x000D_

...because of this behavior, you need to be careful about closing over variables declared using var in loops. There is only one instance of variable x declared here, and it sits logically outside of the loop.

The following prints 5, five times, and then prints 5 a sixth time for the console.log outside the loop:

_x000D_
_x000D_
for(var x = 0; x < 5; ++x) {
    setTimeout(() => console.log(x)) // closes over the `x` which is logically positioned at the top of the enclosing scope, above the loop
}
console.log(x) // note: visible outside the loop
_x000D_
_x000D_
_x000D_

The following prints undefined because x is block-scoped. The callbacks are run one by one asynchronously. New behavior for let variables means that each anonymous function closed over a different variable named x (unlike it would have done with var), and so integers 0 through 4 are printed.:

_x000D_
_x000D_
for(let x = 0; x < 5; ++x) {
    setTimeout(() => console.log(x)) // `let` declarations are re-declared on a per-iteration basis, so the closures capture different variables
}
console.log(typeof x) // undefined
_x000D_
_x000D_
_x000D_

The following will NOT throw a ReferenceError because the visibility of x is not constrained by the block; it will, however, print undefined because the variable has not been initialised (because of the if statement).

_x000D_
_x000D_
if(false) {
    var x = 1
}
console.log(x) // here, `x` has been declared, but not initialised
_x000D_
_x000D_
_x000D_

A variable declared at the top of a for loop using let is scoped to the body of the loop:

_x000D_
_x000D_
for(let x = 0; x < 10; ++x) {} 
console.log(typeof x) // undefined, because `x` is block-scoped
_x000D_
_x000D_
_x000D_

The following will throw a ReferenceError because the visibility of x is constrained by the block:

_x000D_
_x000D_
if(false) {
    let x = 1
}
console.log(typeof x) // undefined, because `x` is block-scoped
_x000D_
_x000D_
_x000D_

Variables declared using var, let or const are all scoped to modules:

// module1.js

var x = 0
export function f() {}

//module2.js

import f from 'module1.js'

console.log(x) // throws ReferenceError

The following will declare a property on the global object because variables declared using var within the global context are added as properties to the global object:

_x000D_
_x000D_
var x = 1
console.log(window.hasOwnProperty('x')) // true
_x000D_
_x000D_
_x000D_

let and const in the global context do not add properties to the global object, but still have global scope:

_x000D_
_x000D_
let x = 1
console.log(window.hasOwnProperty('x')) // false
_x000D_
_x000D_
_x000D_

Function parameters can be considered to be declared in the function body:

_x000D_
_x000D_
function f(x) {}
console.log(typeof x) // undefined, because `x` is scoped to the function
_x000D_
_x000D_
_x000D_

Catch block parameters are scoped to the catch-block body:

_x000D_
_x000D_
try {} catch(e) {}
console.log(typeof e) // undefined, because `e` is scoped to the catch block
_x000D_
_x000D_
_x000D_

Named function expressions are scoped only to the expression itself:

_x000D_
_x000D_
(function foo() { console.log(foo) })()
console.log(typeof foo) // undefined, because `foo` is scoped to its own expression
_x000D_
_x000D_
_x000D_

In non-strict mode, implicitly defined properties on the global object are globally scoped. In strict mode, you get an error.

_x000D_
_x000D_
x = 1 // implicitly defined property on the global object (no "var"!)

console.log(x) // 1
console.log(window.hasOwnProperty('x')) // true
_x000D_
_x000D_
_x000D_

In non-strict mode, function declarations have function scope. In strict mode, they have block scope.

_x000D_
_x000D_
'use strict'
{
    function foo() {}
}
console.log(typeof foo) // undefined, because `foo` is block-scoped
_x000D_
_x000D_
_x000D_

How it works under the hood

Scope is defined as the lexical region of code over which an identifier is valid.

In JavaScript, every function-object has a hidden [[Environment]] reference that is a reference to the lexical environment of the execution context (stack frame) within which it was created.

When you invoke a function, the hidden [[Call]] method is called. This method creates a new execution context and establishes a link between the new execution context and the lexical environment of the function-object. It does this by copying the [[Environment]] value on the function-object, into an outer reference field on the lexical environment of the new execution context.

Note that this link between the new execution context and the lexical environment of the function object is called a closure.

Thus, in JavaScript, scope is implemented via lexical environments linked together in a "chain" by outer references. This chain of lexical environments is called the scope chain, and identifier resolution occurs by searching up the chain for a matching identifier.

Find out more.

Examples related to javascript

need to add a class to an element How to make a variable accessible outside a function? Hide Signs that Meteor.js was Used How to create a showdown.js markdown extension Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Summing radio input values How to execute an action before close metro app WinJS javascript, for loop defines a dynamic variable name Getting all files in directory with ajax

Examples related to function

$http.get(...).success is not a function Function to calculate R2 (R-squared) in R How to Call a Function inside a Render in React/Jsx How does Python return multiple values from a function? Default optional parameter in Swift function How to have multiple conditions for one if statement in python Uncaught TypeError: .indexOf is not a function Proper use of const for defining functions in JavaScript Run php function on button click includes() not working in all browsers

Examples related to variables

When to create variables (memory management) How to print a Groovy variable in Jenkins? What does ${} (dollar sign and curly braces) mean in a string in Javascript? How to access global variables How to initialize a variable of date type in java? How to define a variable in a Dockerfile? Why does foo = filter(...) return a <filter object>, not a list? How can I pass variable to ansible playbook in the command line? How do I use this JavaScript variable in HTML? Static vs class functions/variables in Swift classes?

Examples related to scope

Angular 2 - Using 'this' inside setTimeout Why Is `Export Default Const` invalid? How do I access previous promise results in a .then() chain? Problems with local variable scope. How to solve it? Why is it OK to return a 'vector' from a function? Uncaught TypeError: Cannot read property 'length' of undefined Setting dynamic scope variables in AngularJs - scope.<some_string> How to remove elements/nodes from angular.js array Limiting number of displayed results when using ngRepeat A variable modified inside a while loop is not remembered

Examples related to var

how to display a javascript var in html body ReferenceError: variable is not defined Initialize value of 'var' in C# to null What is /var/www/html? How can I write these variables into one line of code in C#? Why should I use var instead of a type? What is the equivalent of the C# 'var' keyword in Java? PHPDoc type hinting for array of objects? What's the difference between using "let" and "var"? What is the scope of variables in JavaScript?