Variables#

JavaScript has three declaration forms: const (immutable binding), let (block-scoped mutable), and var (function-scoped legacy). Reach for const by default, let only when the binding genuinely changes, and var never in new code.

/types`.

Declarations#

Form

Notes

const

Block scope. The binding cannot be reassigned; the object it points at can still mutate.

let

Block scope. Reassignable. Inside for, each iteration gets a fresh binding (important for closures).

var

Function scope. Hoisted to the top of the function and initialised to undefined. Do not write var in new code.

const max = 100;               // cannot reassign
let count = 0;                 // can reassign
const arr = [1, 2, 3];
arr.push(4);                   // still allowed; binding unchanged

const cfg = Object.freeze({port: 80});
cfg.port = 8080;               // silently ignored in non-strict;
                               // throws under "use strict"

Assignment#

= binds. Destructuring assignment binds many names at once from an array or object on the right-hand side.

let a = 1, b = 2;
[a, b] = [b, a];               // swap

const {host, port = 80} = config;   // destructure object
const [first, ...rest]   = items;   // destructure array

const {data: payload} = response;   // rename on the way out

Scope#

JavaScript is lexically scoped. const and let bind to the nearest enclosing block ({ ... }, function body, for init, module body). var binds to the nearest function or script.

{
  const secret = "rk";
  console.log(secret);         // visible here
}
console.log(secret);           // ReferenceError; out of scope

The temporal dead zone (TDZ) is the gap between the start of the block and the let / const declaration. Reading the binding in that window throws ReferenceError; var does not have a TDZ (it reads as undefined).

console.log(x);     // ReferenceError
const x = 1;

console.log(y);     // undefined; var is hoisted but uninitialised
var y = 1;

Closures capture the binding, not the value, so a var inside a for over an async body shares one slot.

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0);   // prints 3 3 3
}
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0);   // prints 0 1 2
}

Globals#

Globals live on globalThis (window in browsers, global in Node). Bare x = 1 (without let / const / var) creates a global in non-strict mode and throws under "use strict" and ES modules.

globalThis.answer = 42;
console.log(answer);           // 42

ES modules are strict by default. Do not create globals; everything is imported and exported explicitly.

References#

  • Syntax for the lexical rules let / const / var parse against.

  • Types for what each binding can hold.

  • Functions for closures and the binding-vs-value capture rule in full.

  • Runtime for globalThis and module scope.

  • MDN, let.