Variables
Declare variables explicitly, use one declaration per variable, group const before let, and avoid patterns that silently create globals.
13.1 Always use const or let
eslint: no-undef, prefer-const
Not declaring variables with const or let results in global variables. Avoid polluting the global namespace.
const superPower = new SuperPower();
superPower = new SuperPower();
13.2 One const or let declaration per variable
eslint: one-var
It’s easier to add new variable declarations this way, and you never have to worry about swapping out a ; for a , or introducing punctuation-only diffs. You can also step through each declaration with the debugger, instead of jumping through all of them at once.
const items = getItems();
const goSportsTeam = true;
const dragonball = 'z';
// bad
const items = getItems(),
goSportsTeam = true,
dragonball = 'z';
// bad - spot the mistake
const items = getItems(),
goSportsTeam = true;
dragonball = 'z';
13.3 Group consts before lets
This is helpful when later you might need to assign a variable depending on one of the previously assigned variables.
const goSportsTeam = true;
const items = getItems();
let dragonball;
let i;
let length;
// bad
let i, len, dragonball,
items = getItems(),
goSportsTeam = true;
// bad
let i;
const items = getItems();
let dragonball;
const goSportsTeam = true;
let len;
13.4 Assign variables where you need them
Assign variables where you need them, but place them in a reasonable place. let and const are block scoped and not function scoped.
function checkName(hasName) {
if (hasName === 'test') {
return false;
}
const name = getName();
if (name === 'test') {
this.setName('');
return false;
}
return name;
}
// bad - unnecessary function call
function checkName(hasName) {
const name = getName();
if (hasName === 'test') {
return false;
}
if (name === 'test') {
this.setName('');
return false;
}
return name;
}
13.5 Don’t chain variable assignments
eslint: no-multi-assign
Chaining variable assignments creates implicit global variables.
(function example() {
let a = 1;
let b = a;
let c = a;
}());
console.log(a); // throws ReferenceError
console.log(b); // throws ReferenceError
console.log(c); // throws ReferenceError
(function example() {
// JavaScript interprets this as
// let a = ( b = ( c = 1 ) );
// The let keyword only applies to variable a; variables b and c become
// global variables.
let a = b = c = 1;
}());
console.log(a); // throws ReferenceError
console.log(b); // 1
console.log(c); // 1
Chained assignment like let a = b = c = 1 makes b and c implicit globals, even inside a function. This is a subtle and dangerous bug.
13.6 Avoid unary increments and decrements
eslint: no-plusplus
Unary increment and decrement statements (++, --) are subject to automatic semicolon insertion and can cause silent errors. num += 1 is more expressive and unambiguous than num++ or num ++. Disallowing them also prevents unintentional pre-increment/pre-decrement.
const array = [1, 2, 3];
let num = 1;
num += 1;
num -= 1;
const sum = array.reduce((a, b) => a + b, 0);
const truthyCount = array.filter(Boolean).length;
const array = [1, 2, 3];
let num = 1;
num++;
--num;
let sum = 0;
let truthyCount = 0;
for (let i = 0; i < array.length; i++) {
let value = array[i];
sum += value;
if (value) {
truthyCount++;
}
}
13.7 Avoid linebreaks around = in assignments
eslint: operator-linebreak
Linebreaks surrounding = can obfuscate the value of an assignment. If your assignment violates max-len, surround the value in parens.
const foo = (
superLongLongLongLongLongLongLongLongFunctionName()
);
const foo = 'superLongLongLongLongLongLongLongLongString';
const foo =
superLongLongLongLongLongLongLongLongFunctionName();
const foo
= 'superLongLongLongLongLongLongLongLongString';
13.8 Disallow unused variables
eslint: no-unused-vars
Variables that are declared and not used anywhere in the code are most likely an error due to incomplete refactoring. Such variables take up space in the code and can lead to confusion by readers.
function getXPlusY(x, y) {
return x + y;
}
const x = 1;
const y = a + 2;
alert(getXPlusY(x, y));
// 'type' is ignored even if unused because it has a rest property sibling.
// This is a form of extracting an object that omits the specified keys.
const { type, ...coords } = data;
// 'coords' is now the 'data' object without its 'type' property.
const some_unused_var = 42;
// Write-only variables are not considered as used.
let y = 10;
y = 5;
// A read for a modification of itself is not considered as used.
let z = 0;
z = z + 1;
// Unused function arguments.
function getX(x, y) {
return x;
}
Hoisting
Understanding hoisting explains why declaration placement matters and why const and let are safer than var.
14.1 How var hoisting works
var declarations get hoisted to the top of their closest enclosing function scope, but their assignment does not. const and let declarations enter a Temporal Dead Zone (TDZ) — they are hoisted but not accessible until the declaration is reached.
// we know this wouldn't work (assuming there
// is no notDefined global variable)
function example() {
console.log(notDefined); // => throws a ReferenceError
}
// creating a variable declaration after you
// reference the variable will work due to
// variable hoisting. Note: the assignment
// value of `true` is not hoisted.
function example() {
console.log(declaredButNotAssigned); // => undefined
var declaredButNotAssigned = true;
}
// the interpreter is hoisting the variable
// declaration to the top of the scope,
// which means our example could be rewritten as:
function example() {
let declaredButNotAssigned;
console.log(declaredButNotAssigned); // => undefined
declaredButNotAssigned = true;
}
// using const and let
function example() {
console.log(declaredButNotAssigned); // => throws a ReferenceError
console.log(typeof declaredButNotAssigned); // => throws a ReferenceError
const declaredButNotAssigned = true;
}
With const and let, even typeof throws a ReferenceError inside the Temporal Dead Zone. This is a break from historical var behavior, where typeof on an undeclared variable safely returns "undefined".
14.2 Anonymous function expressions hoist the variable name only
Anonymous function expressions hoist their variable name, but not the function assignment.
function example() {
console.log(anonymous); // => undefined
anonymous(); // => TypeError anonymous is not a function
var anonymous = function () {
console.log('anonymous function expression');
};
}
14.3 Named function expressions hoist the variable name only
Named function expressions hoist the variable name, not the function name or the function body.
function example() {
console.log(named); // => undefined
named(); // => TypeError named is not a function
superPower(); // => ReferenceError superPower is not defined
var named = function superPower() {
console.log('Flying');
};
}
// the same is true when the function name
// is the same as the variable name.
function example() {
console.log(named); // => undefined
named(); // => TypeError named is not a function
var named = function named() {
console.log('named');
};
}
14.4 Function declarations hoist their name and body
Function declarations hoist both their name and the function body, making them callable before the declaration in the source.
function example() {
superPower(); // => Flying
function superPower() {
console.log('Flying');
}
}
Even though function declarations are hoisted, relying on this behavior makes code harder to read. Rule 14.5 requires you to define things before you use them.
14.5 Define variables, classes, and functions before using them
eslint: no-use-before-define
When variables, classes, or functions are declared after being used, it harms readability since a reader won’t know what a referenced thing is. It’s much clearer for a reader to first encounter the source of a thing before encountering a use of it.
var a = 10;
console.log(a); // 10
function fun() {}
fun();
class A {
}
new A();
let a = 10;
const b = 5;
console.log(a); // 10
console.log(b); // 5
// Variable a is being used before it is being defined.
console.log(a); // this will be undefined, since while the declaration is hoisted, the initialization is not
var a = 10;
// Function fun is being called before being defined.
fun();
function fun() {}
// Class A is being used before being defined.
new A(); // ReferenceError: Cannot access 'A' before initialization
class A {
}
// `let` and `const` are hoisted, but they don't have a default initialization.
// The variables 'a' and 'b' are in a Temporal Dead Zone where JavaScript
// knows they exist (declaration is hoisted) but they are not accessible
// (as they are not yet initialized).
console.log(a); // ReferenceError: Cannot access 'a' before initialization
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let a = 10;
const b = 5;