Functions and scope

JavaScript Scope

Learn how JavaScript scope decides where a variable is visible, covering global, function and block scope and how var differs from let and const.

Scope is the set of variables visible in a part of your program. JavaScript uses lexical scoping: a variable’s meaning comes from where you wrote it, not from where you call the code later.

We talk about global scope, function scope, and block scope. A variable declared outside any function or block attaches to the global object and is visible everywhere.

var, let, and const do not behave the same inside functions and blocks.

A var inside a function is visible through the whole function, like a parameter. A let or const inside a block is visible only in that block.

Blocks { } create scope for let and const, but not for var. Only a function body creates a new var scope.

Inside a function, var declarations hoist to the top of that function. You can reference the name before the assignment line, which often surprises people:

function run() {
  console.log(`${name}`)
  var name = 'Flavio'
}

run()

This prints undefined, because the engine effectively runs:

function run() {
  var name;
  console.log(`${name}`)
  name = 'Flavio'
}

run()

let and const stay in the temporal dead zone until their line runs. The same example with let throws ReferenceError: name is not defined.

Inner functions can read variables from outer functions. That link is called a closure, and we cover it in the next lesson.

In non-strict mode, assigning to an undeclared name creates a global property. That is a common bug source. I run strict mode by default so the mistake fails fast.

A local declaration with the same name as a global shadows the global.

With var:

var name = 'Roger'

function run() {
  console.log(`${name}`)
  var name = 'Flavio'
}

run()

This prints undefined, not 'Roger'.

With let:

let name = 'Roger'

function run() {
  console.log(`${name}`)
  let name = 'Flavio'
}

run()

This throws ReferenceError: name is not defined, because the inner let is in the temporal dead zone for the whole block.

Try both snippets in the console. The difference is why I reach for let and const instead of var in new code.

Lesson completed