Strict Mode

Happy new year! I'm kicking 2022 off with a post that has been in my queue for quite some time, an exploration of strict mode in JavaScript.

Throughout my years as a developer I've seen the little directive "use strict" at the top of a lot of JavaScript files. I first saw it in my bootcamp days. When I'd ask about it, my teachers and TAs often just explained that it put the code into something called strict mode, but that I shouldn't worry about it just yet. I ran across it again in various projects or reading through You Don't Know JS, and started to understand that it affected things like how variables could or could not be declared. Finally, when I started using a linter more frequently I found that most of the common ESLint style guides enforce using strict mode everywhere. Its ubiquity made me want to learn all about it. So this post is meant to be a guide to strict mode in JavaScript – what it is, how you use it, why it exists, what the benefits of using it are, and a nice, tidy summary of what specifically it does. Let's jump in.

What is Strict Mode?

In 2009, ES5 introduced the "use strict" directive to JavaScript. This directive defines that the JavaScript code should be executed in "strict mode". When you're working in strict mode, certain errors that would normally be silently allowed by JavaScript are turned into properly thrown errors, calling attention to poorly written code. The errors and anti-patterns that strict mode polices generally revolve around the manner in which data at the global scope is created, modified, accessed, or deleted. By monitoring whether and when the global object can be accessed, strict mode eliminates code that makes it difficult for JavaScript engines to perform runtime optimizations, protects a program from using syntax that will be defined in future versions of language spec, and generally empowers the developer to deliver "safer" and more "secure" JavaScript.

How Do I Use Strict Mode?

You should be using strict mode for all of your programs, but how do you do so?

To use strict mode, you simply need to add "use strict" to the beginning of a script or a function.

When added at the beginning of a script or a JavaScript file, it will have global scope. This means that all code in the script (or in that file) will execute in strict mode.

"use strict";

person = "Joey"; // ReferenceError: person is not defined

Strict Mode for Functions

You can also opt into strict mode for specific functions:

person = "Joey"; // Allowed because we're not in strict mode here

function sayHi(name) {
    "use strict";

    greeting = `Hi ${name}`; //ReferenceError: greeting is not defined

    console.log(greeting);
}

sayHi(person);

When declared inside of a function, strict mode will only be scoped locally to that function.

An additional note here is that the "use strict" directive will only be recognized at the beginning of a script, file, or function. If you try to declare strict mode halfway through a function, it will not be valid.

Strict Mode for Modules

JavaScript modules are an ES2015 feature, and the entire contents of JavaScript modules are automatically in strict mode. You don't need to declare it to initiate it!

function doSomething() {
  // this function is strict by default because it's a module
}

export default doSomething;

Strict Mode for Classes

Like modules, JavaScript classes are also strict by default.

class Car {
  // this class and everything about it is strict by default

  constructor(model, year) {
    this.model = model;
    this.year = year;
  }
}

Why Does Strict Mode Exist?

Strict mode exists because JavaScript is a quirky and imperfect language. Originally intended as a scripting language to handle user interactions on the front end of websites, JavaScript has grown in leaps and bounds to be a fully featured language available for use at all levels of the stack. However, some historical features of the language are no longer best practice, but these features are used so broadly throughout the internet that they cannot harmlessly be outright deleted. Or there are coding practices that produce errors that may not be readily apparent to a developer.

Strict mode does a lot of work to protect against these fuzzier edges of the language. But to go deeper, the benefits of strict mode largely fall into five broad categories: code quality, performance improvements, simplifying eval() and arguments, delivering more "secure" JavaScript, and future-proofing JavaScript for upcoming EcmaScript releases.

Code Quality

Improved code quality is probably a fairly self-evident benefit of using strict mode. JavaScript doesn't compile in the same way that a language like Java does, and thereby doesn't get a lot in the way of compilation time error reporting. There is endless debate over best practices and language semantics, and JavaScript developers often look to things like prettier and linters (and all of the various rules therein) to enforce code quality. It's actually pretty nice that strict mode is a formal part of the language, so you don't need to integrate an external tool or abide by someone else's rules just to deliver better code.

Take the following code for example:

var person = "Joey";

peason = "Joseph";

console.log(person); // "Joey"
console.log(peason); // "Joseph"

In this code we have declared a variable, person, and given it the value "Joey". We then try to reassign the value to equal "Joseph", but we misspell person as peason. In doing so we have accidentally created a new variable in the global scope named peason in addition to the person variable we had already declared. Our scope is polluted with an unwanted variable and the variable we were working with doesn't contain the value that we intend it to.

Now let's try to execute that same code in strict mode:

"use strict";

var person = "Joey";

peason = "Joseph"; // ReferenceError: peason is not defined

Now when we run that code, we get a ReferenceError telling us that peason is not defined. That is because we have not formally declared a variable by that name. Strict mode puts bumpers like this up to guard from scope pollution, make our code adhere to some safety guidelines, and make sure the code we write is generally more optimizable by the JavaScript engine.

This is an example of one of the best-known rules in strict mode: that you cannot use a variable or object without formally declaring it using var, let, or const. Where this kind of code would have passed silently before, under strict mode it throws an error.

A similar but less common coding technique is in chained variable assignment. Consider the following:

var person = newPerson = "Joey";

console.log(person); // Expected result: "Joey"
console.log(newPerson); // Expected result: "Joey"

Even though we are formally declaring the person variable with the var keyword, we are not formally declaring newPerson. In a non-strict mode environment, if newPerson is not formally declared elsewhere, then this code quietly creates the global newPerson variable, and both person and newPerson are given the value "Joey".

However, if we try the same in strict mode:

"use strict";
var person = newPerson = "Joey"; // ReferenceError: newPerson is not defined

We get the same ReferenceError we observed earlier.

It will similarly throw formal errors if you assign values to non-writable parts of the language, like certain global variable identifiers or object properties:

Without strict mode:

// Assignment to a non-writable global
var undefined = 100;
console.log(undefined); // Expected result: undefined - value is not reassigned but no error is thrown
var Infinity = 12;
console.log(Infinity); // Expected result: Infinity - value is not reassigned but no error is thrown

// Assignment to a non-writable object property
const person = {};
Object.defineProperty(person, 'age', {
  value: 33,
  writable: false
});
person.age = 34;
console.log(person.age); // Expected result: 33 - value is not reassigned but no error is thrown

// Assignment to a shadowed non-writable object property
const newPerson = Object.create(person);
newPerson.age = 34;
console.log(newPerson.age); // Expected result: 33 - value is not reassigned but no error is thrown

// Assignment to a getter-only object property
const car = {
  get year() {
    return 2013;
  }
}
car.year = 2005;
console.log(car.year); // Expected result: 2013 - value is not reassigned but no error is thrown

// Assignment to a new property on a non-extensible object
const fixedObject = {};
Object.preventExtensions(fixedObject);
fixedObject.someNewProperty = "howdy";
console.log(fixedObject.someNewProperty); // Expected result: undefined - the new property was not created but no error is thrown

In each of the cases above, the various errors and anti-patterns passed without the language throwing any errors. Now let's work in strict mode and see what happens:

With strict mode:

"use strict";
// Assignment to a non-writable global
var undefined = 100; // TypeError
var Infinity = 12; // TypeError

// Assignment to a non-writable object property
const person = {};
Object.defineProperty(person, 'age', {
  value: 33,
  writable: false
});
person.age = 34; // TypeError

// Assignment to a shadowed non-writable object property
const newPerson = Object.create(person);
newPerson.age = 34;
console.log(newPerson.age); // TypeError

// Assignment to a getter-only object property
const car = {
  get year() {
    return 2013;
  }
}
car.year = 2005; // TypeError

// Assignment to a new property on a non-extensible object
const fixedObject = {};
Object.preventExtensions(fixedObject);
fixedObject.someNewProperty = "howdy"; // TypeError

Deleting undeletable properties also throws an error in strict mode, where before it would be silently ignored:

Without strict mode:

const person = {};
Object.defineProperty(person, 'age', {
  value: 33,
  configurable: false,
});
delete person.age; // silently ignored, age property still exists on person object

With strict mode:

"use strict";
const person = {};
Object.defineProperty(person, 'age', {
  value: 33,
  configurable: false,
});
delete person.age; // TypeError

Strict mode also complains if a function is written with non-unique parameter names. Take the following code:

Without strict mode:

function sum(a, a, c) { // a is duplicated, will silently accept whatever the last argument value is
  return a + a + c;
}

sum(1, 2, 3); // 7; a is set to 2, so 2 + 2 + 3 = 7

This is a confusing way to write a function declaration, and it makes it somewhat non-deterministic which value will actually be used for a. When the function is called, it will use the last duplicated argument, hiding the previous identically-named argument(s).

But even more puzzling, that earlier duplicate argument value is still available:

function sum(a, a, c) {
  Array.from(arguments).map(argument => {
    console.log(argument); // 1, 2, 3
  });
  return a + a + c;
}

sum(1, 2, 3);

At any rate, strict mode will (thankfully) throw an error if you have duplicate parameter names:

With strict mode:

"use strict";
function sum(a, a, c) { // SyntaxError
  return a + a + c;
}

sum(1, 2, 3);

Another change that strict mode enforces that falls under the code quality umbrella is one I don't really understand as well, but I'll mention it anyway. It involves octal literals and octal escape sequences. These relate to numbers with leading zeros.

From MDN:

Outside strict mode, a number beginning with a 0, such a 0644, is interpreted as an octal number (0644 === 420) if all digits are smaller than 8.

Octal escape sequences, such as "\45", which is equal to "%", can be used to represent characters by extended-ASCII character code numbers in octal. ... In ECMAScript 2015, octal literals are supported by prefixing a number with "0o"; for example: var a = 0o10;

With that in mind, where this comes up (in a way that is clear to me) is in prepending numbers with leading zeroes for alignment purposes:

Without strict mode:

var sum = 015 +
          197 +
          142;
console.log(sum); // Result: 352; not 354!!

You would think the sum would be 354, since 15 + 197 + 142 is 354. However, by adding the leading zero to 15, we actually are using an octal number. And 015 === 13, so ultimately we are adding 13 + 197 + 142, giving us the sum total of 352.

With strict mode:

"use strict";
var sum = 015 + // SyntaxError
          197 +
          142;
console.log(sum);

If, however, you do need to use an octal number in strict mode, this is permitted if you use the octal escape sequence:

"use strict";
var sum = 0o15 +
          197 +
          142;
console.log(sum); // Result: 352;
console.log(0o15 === 13); // Result: true

The last change that strict mode makes that falls under the code quality umbrella is that strict mode forbids setting properties on primitive values.

Without strict mode:

function changePrimitives() {
  false.true = ``; // Silently ignored
  (33).gandalf = 'wizard'; // Silently ignored
  'tony'.carmella = 'ziti'; // Silently ignored
}

changePrimitives();

With strict mode:

"use strict";
function changePrimitives() {
  false.true = ``; // TypeError
  (33).gandalf = 'wizard'; // TypeError
  'tony'.carmella = 'ziti'; // TypeError
}

changePrimitives();

It's worth noting that these errors are what would be considered "early errors", or errors that are thrown during compile time, not during runtime. In other words, the errors here won't be caught in a try...catch block. When you try to run the code, the code just won't run at all. This isn't necessarily unique to the rules imposed by strict mode, as many environments won't run any code that has huge, glaring syntax errors. Instead, you can think of strict mode as something that adds to the list of "early errors".

Performance Improvements

Beyond helping a developer with their (presumed) general desire to write better code, strict mode confers performance benefits. By safeguarding against certain patterns, the optimization process that happens when the code compiles at runtime is actually improved.

In non-strict mode, things like the with statement or the eval() function can allow for variables to be declared at non-deterministic memory locations. As such, this code needs to actually start runtime for the program to be able to figure out where the variable is actually scoped and what its value is.

Without strict mode:

The with keyword is a shorthand way for updating multiple properties in an object:

var person = {
  age: 33,
  favoriteNumber: 66,
  secondFavoriteNumber: 42,
}

with (person) {
  age = 34;
  favoriteNumber = 666;
  secondFavoriteNumber = 420;
}

console.log(person.age);
// expected result: 34

The problem with the with keyword comes in if you use it to reference an identifier that is not a property on the object that gets passed. The with keyword actually treats the object it's passed as its own lexical scope, and if it doesn't find the identifier referenced, it behaves like any other var variable reference: it looks up the lexical scope chain for that identifier, and if one is not found, it silently creates that variable at the global scope:

var person1 = {
  age: 33,
  favoriteNumber: 66,
  secondFavoriteNumber: 42,
}

var person2 = {
  age: 52,
  favoriteNumber: 70,
}

function updateSecondFavoriteNumber(obj) {
  with (obj) {
    secondFavoriteNumber = 420;
  }
}

updateSecondFavoriteNumber(person1);
console.log(person1.secondFavoriteNumber);
// expected result: 420

updateSecondFavoriteNumber(person2);
console.log(person2.secondFavoriteNumber);
// expected result: undefined

console.log(secondFavoriteNumber);
// expected result: 420

console.log(window.secondFavoriteNumber);
// expected result: 420

With strict mode:

Strict mode restricts usage of with altogether:

"use strict";
var person = {
  age: 33,
  favoriteNumber: 66,
  secondFavoriteNumber: 42,
}

with (person) { // SyntaxError
  age = 34;
  favoriteNumber = 666;
  secondFavoriteNumber = 420;
}

ES6 introduces the @@unscopables Symbol, which defines which properties on an object can and cannot be exposed as local variables in a with statement. This behavior can best be seen when you have global variables with the same name as the properties on an object:

var age = 330;
var favoriteNumber = 666;
var secondFavoriteNumber = 420;

var person = {
  age: 33,
  favoriteNumber: 66,
  secondFavoriteNumber: 42,
}

person[Symbol.unscopables] = {
  age: false,
  favoriteNumber: true,
  secondFavoriteNumber: false,
}

with (person) {
  console.log(age); // expected result: 33
  console.log(favoriteNumber); // expected result: 666
  console.log(secondFavoriteNumber); // expected result: 42
}

person.age and person.secondFavoriteNumber have their @@unscopables set to false, meaning they are scoped to their object's lexical scope. Because of that, the property values from the person object (33 and 42) are logged within the with statement. person.favoriteNumber on the other hand has its @@unscopables set to false, so the with statement ignores the favoriteNumber value that would have been set to the person lexical scope, and instead logs the value of the global favoriteNumber variable.

But again, when all of this is run in strict mode, the with statement errors out:

"use strict";
var age = 330;
var favoriteNumber = 666;
var secondFavoriteNumber = 420;

var person = {
  age: 33,
  favoriteNumber: 66,
  secondFavoriteNumber: 42,
}

person[Symbol.unscopables] = {
  age: false,
  favoriteNumber: true,
  secondFavoriteNumber: false,
}

with (person) { // SyntaxError
  console.log(age);
  console.log(favoriteNumber);
  console.log(secondFavoriteNumber);
}

Similarly, the eval() function in non-strict mode can introduce variables into scope outside of the eval() function, including the global scope.

Without strict mode:

var age = 33;
var evalAge = eval("var age = 34; age;");

console.log(age === 33); // false
console.log(evalAge === 34); // true

This is also non-deterministic behavior, and feels like it goes against everything I know about scoping. While I think it's generally accepted that it's best practice to not use eval() at all, strict mode helps make sure that eval() does not introduce new variables into the surrounding scope.

With strict mode:

var age = 33;
var evalAge = eval("'use strict'; var age = 34; age;");

console.log(age === 33); // true
console.log(evalAge === 34); // true

By restricting use of with and eliminating eval()'s ability to pollute its surrounding scope, variable mapping is deterministic and predictable enough to allow the compiler to be far more performant. Code that is run in strict mode will often run much faster than identical code that is not run in strict mode.

Beyond restricting how variables are declared and created, strict mode also has restrictions about when data is deleted.

For example, in strict mode you cannot delete a variable or object:

Without strict mode:

var age = 33;
var person = {
  name: 'Joey',
  age,
}

delete person; // Silently ignored
delete age; // Silently ignored

With strict mode:

"use strict";
var age = 33;
var person = {
  name: 'Joey',
  age,
}

delete person; // SyntaxError
delete age; // SyntaxError

Related to this, you cannot delete a function in strict mode.

Without strict mode:

var x = () => { console.log("hi") };
x(); // hi;

delete x; // returns false

With strict mode:

"use strict";
var x = () => { console.log("hi") };
x(); // hi;

delete x; // SyntaxError

In these cases the better practice is to set the value of the variable you want deleted to null, and let garbage collection take care of disposal.

Simplifies eval() and arguments

I don't use eval() or arguments a whole ton, so I won't act like I know much about what's going on here. Even MDN refers to these two language features as "bizarrely magical." Strict mode seems to try to reign in that bizarre magic to make them behave more predictably.

To accomplish that, for one thing you cannot use eval or arguments as variable names in strict mode. Attempts to assign or bind to eval or arguments will result in SyntaxErrors.

Beyond that, strict mode doesn't allow aliasing of properties of the arguments object:

Without strict mode:

function doSomething(param) {
  param = 100;
  return [param, arguments[0]];
}

const check = doSomething(50);
console.log(check[0] === 100); // true
console.log(check[1] === 50); // false
console.log(check); // [100, 100];

With strict mode:

"use strict";
function doSomething(param) {
  param = 100;
  return [param, arguments[0]];
}

const check = doSomething(50);
console.log(check[0] === 100); // true
console.log(check[1] === 50); // true
console.log(check); // [100, 50];

The final note under this umbrella is that arguments.callee is no longer supported in strict mode. This is an optimization thing.

Without strict mode:

var doSomething = function() {
  return arguments.callee; // f () { return arguments.callee; }
}

doSomething();

In strict mode, argument.callee is made into a non-deletable property which throws an error when set or retrieved:

With strict mode:

"use strict";
var doSomething = function() {
  return arguments.callee; // TypeError
}

doSomething();

Delivers More “Secure" JavaScript

In front end, client-facing code, strict mode also helps in "securing" JavaScript. In non-strict mode JavaScript, the this keyword is always forced into being a JavaScript object, even if its value is a non-object (like a Boolean, string, or a number). This process is called "boxing". The boxing process is resource-heavy, so it comes with a performance cost. But beyond that, if a function is called with an undefined or null this, then the object it binds to could very well be the global object or the window. This presents a security hazard! Strict mode will not box a specified this to an object, and if it's unspecified, this will return undefined.

A second security benefit is in disallowing the caller and arguments properties within functions. These properties, now deprecated (though still in the language) return data about a function's invocation, namely the function that most recently called the called function, and its arguments.

Without strict mode:

function calledFunction() {
  console.log(calledFunction.caller); // function callerFunction()
  console.log(calledFunction.arguments); // Arguments ['hi']
}

function callerFunction() {
  calledFunction("hi");
}

callerFunction();

The information that .caller and .arguments makes available is privileged, so strict mode will instead throw a TypeError if these properties are called.

With strict mode:

function calledFunction() {
  "use strict";
  console.log(calledFunction.caller); // throws a TypeError
  console.log(calledFunction.arguments); // throws a TypeError
}

function callerFunction() {
  calledFunction("hi");
}

callerFunction();

For similar reasons/contexts, strict mode disallows access to certain properties on the arguments object inside of a function in strict mode. This is seldom seen these days as the problematic properties have largely been removed from recent browser implementations of JavaScript.

Future-proofing JavaScript for Upcoming EcmaScript Releases

The last benefit of strict mode (and probably the most straightforward) is in future-proofing the way for future versions of the language. Future releases of JavaScript will have new features that use new syntax and keywords (or identifiers). Using strict mode mode now helps this process by designating identifiers as reserved keywords. As such, you cannot use these identifiers as variable names. The list (so far) includes: implements, interface, let, package, private, protected, public, static, and yield.

Summary: Strict Mode

As a quick reference, here is a list of specific changes that strict mode makes:

Improves Code Quality By Throwing Errors

  • You cannot use a variable or object without declaring it using var, let, or const (including chained variable assignments)
  • You cannot write to a non-writable global identifier
  • You cannot write to a read-only object property
  • You cannot write to a shadowed read-only object property
  • You cannot write to a get-only property
  • You cannot delete an undeletable property
  • You cannot duplicate a parameter name
  • You cannot use octal numeric literals (i.e., numbers with a leading 0)
    • Use octal escape sequences instead
  • You cannot set properties on primitive values

Improves Performance & Simplifies Variable Usage

  • You cannot use the with statement
  • You cannot create variables using the eval() function
  • You cannot delete a variable or object
  • You cannot delete a function

Simplifies eval() and arguments

  • You cannot use any of the following reserved keywords as variable names:
    • eval
    • arguments
  • You cannot alias properties of arguments objects
  • You cannot use arguments.callee

Delivers More “Secure" JavaScript

  • Different behavior for the this keyword:
    • The this keyword typically refers to the object that called the function (read more in my What is This? Blog Post)
    • If the object is not specified, functions in strict mode will return undefined
    • Functions in normal mode will return the global object (i.e., the window)
  • You cannot "walk" the JavaScript stack by using .caller or .arguments properties or things like arguments.caller within a function

Future-proofs JavaScript for Upcoming EcmaScript Releases

  • You cannot use any of the following reserved keywords as variable names:
    • implements
    • interface
    • let
    • package
    • private
    • protected
    • public
    • static
    • yield

From MDN:

JavaScript's strict mode, introduced in ECMAScript 5, is a way to opt in to a restricted variant of JavaScript, thereby implicitly opting-out of "sloppy mode". Strict mode isn't just a subset: it intentionally has different semantics from normal code. Browsers not supporting strict mode will run strict mode code with different behavior from browsers that do, so don't rely on strict mode without feature-testing for support for the relevant aspects of strict mode. Strict mode code and non-strict mode code can coexist, so scripts can opt into strict mode incrementally.

Strict mode makes several changes to normal JavaScript semantics:

  1. Eliminates some JavaScript silent errors by changing them to throw errors.
  2. Fixes mistakes that make it difficult for JavaScript engines to perform optimizations: strict mode code can sometimes be made to run faster than identical code that's not strict mode.
  3. Prohibits some syntax likely to be defined in future versions of ECMAScript.

Sources / Further Reading