Function scopes and block scopes in JavaScript

Is the following line a valid line of JavaScript code?


Yes it is.

These are nested block scopes.

This article is based on an interactive lab I created for jsComplete. You can check out the interactive version here.

Block scopes are different from function scopes in JavaScript. A function scope is created for every function (and we can nest them too):

function iHaveScope() {
// local function scope
  function iHaveNestedScope() {
// nested local function scope

We often identify those scopes as local scopes and identify the top-level scope as the global scope. In a browser environment, the global scope is controlled by the window object while in Node.js, it’s controlled by the global object.

It’s hard to completely avoid the global scope, unless you’re coding purely functional style, but you should minimize the use of any global variables because they represent a state and having that defined globally makes it more vulnerable to conflicts and data corruption. You should use an Immediately Invoked Function Expression (IIFE) - when you can - to wrap all your JavaScript code in a local function scope (in Node.js, this is actually done automatically for every module, so you don’t have to do it there):

void function() {
// your code here

Block scopes are what you get when you use if statements, for statements, and the like. You can also use them stand-alone with a simple begin-end curly braces {}, not to be confused with empty object literals.

var a = {} // empty object literal
{ var a } // undefined object in a block scope
if (3 == '3') {
// block scope for the if statement
for (var i=0; i<10; i++) {
// block scope for the for statement

var vs. let

The var keyword behaves differently in function scopes and block scopes. A variable declared with var in a function scope can’t be accessed outside that function scope.

function iHaveScope() {
var secret = 42;
secret; // ReferenceError: secret is not defined (in this scope)

A variable declared with var in a block scope is available outside of that block scope.

for (var i=0; i<10; i++) {
// block scope for the for statement
console.log(i) // => 10 (why oh why)

The i variable that we often use in a for loop will continue to exist beyond the scope of that loop, and that does not make sense, really.

Luckily we now have a different way to declare variables, using let. Variables declared with let inside a block scope are only accessible inside that scope, making let the ideal solution to the for loop index variable scope problem. If we use let to declare the i variable in a for loop, that variable will only be available inside the for loop.

for (let i=0; i<10; i++) {
// block scope for the for statement
console.log(i) // ReferenceError: i is not defined (D'oh!)


Declaring a variable with const is exactly like let - when it comes to scopes - but creates a constant reference for the variable. We can’t change the value of a constant reference. If we put a primitive value in a constant, that value will be protected from getting changed:

const PI = 3.141592653589793
PI = 42 // SyntaxError: "PI" is read-only

Note that if the constant is an object, we can still change the properties of that object, so be careful about that:

const dessert = { type: 'pie' };
dessert.type = 'pudding'; // Sure thing
console.log(dessert.type) // pudding

We can’t however, reassign an object declared with const:

const dessert = { type: 'pie' };
dessert = { type: 'cake' }; // SyntaxError: "dessert" is read-only

If we want a completely immutable object, we’ll have to use something else. My favorite library for that is Immutable.js.

Constants are popularly used when importing things from other libraries so that they don’t get changed accidentally. In Node.js for example, we use it with the require function:

const _ = require('lodash');

Constants are also great to use when defining functions, because we rarely need to update a function after we define it the first time.

In general, I think it’s good to always use const for your declarations and only switch to let or var if you actually need to. With const, you get the peace of mind that no mutating reassignments are happening on a variable, while with let/var, you’ll have to read the code to verify that:

let answer = 42;
// some code ...
// answer MIGHT be 42 here, read the code to be sure.
// ***
// vs.
// ***
const answer = 42;
// some code ...
// answer IS STILL 42 here, no matter what happens above

I hope JavaScript will eventually get more native immutability features baked into the language. While const does not exactly provide full immutability for all objects, it’s a very good start for primitive values.

Thanks for reading.