Command Palette

Search for a command to run...

Hectal
PHASE 14Advanced ~22 min· topic 9 of 9

Topic 14.9

Strict Mode and Language Quirks

In one line

Strict mode, switched on by "use strict" and automatically on in modules and classes, turns silent mistakes into errors, removes a few confusing features and makes this safer; the rest of JavaScript's odd legacy behaviours stay in the language forever, so you learn to recognise and avoid them.

Think of it like this

A teacher who marks every mistake. A relaxed teacher lets small errors slide: a missing word, a misspelt name, and you never learn about them. A strict teacher circles each one in red at once. Strict mode is the strict teacher: code that would quietly do the wrong thing in sloppy mode (the old default) now stops with an error that tells you exactly what went wrong.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Strict mode
A stricter set of language rules that turns silent mistakes into errors. Turned on by "use strict", modules and classes.
Sloppy mode
The informal name for JavaScript's original, non-strict rules, still the default in classic scripts.
Directive
A special string statement like "use strict"; at the start of a script or function that changes how it's run.
Read-only property
A property that can't be changed, such as one on a frozen object or one with only a getter.
Non-extensible object
An object you can't add new properties to, e.g. after Object.freeze or Object.preventExtensions.
Legacy behaviour
An old rule kept in the language so existing websites keep working, even if it's confusing.
Linter
A tool such as ESLint that reads your code and warns about likely mistakes before you run it.

Step by step

01Turn it on

Put the directive first in a file, or first in one function. Anything else first (except comments) and it's just an ignored string.

app.jswhole filejavascript
"use strict";

let total = 0;
totl = 5; // ReferenceError: totl is not defined

02Notice you're often strict already

Modules and class bodies are always strict. That's why a method extracted from a class has this === undefined when called plainly, while an old-style constructor function's method gets window.

Notice you're often strict alreadydiagram
Rendering diagram…

03See a silent bug become loud

Sloppy mode ignored the write to the frozen object, so config.debug stays false and you'd spend an hour wondering why. Strict mode throws at the exact line.

freeze.jswhole filejavascript
"use strict";
const config = Object.freeze({ debug: false });
config.debug = true;
// TypeError: Cannot assign to read only property 'debug' of object '#<Object>'

04Check this in plain calls

A sloppy function called without an object gets window as this. Strict gets undefined. Methods detached from their objects then fail fast instead of touching globals.

05Recognise legacy quirks

Strict mode doesn't change typeof null, == coercion, ID globals or document.all. Know them, avoid relying on them, and let === and a linter keep you safe.

Try it yourself

  1. 1

    Make the whole tab strict

    In the first example, add "use strict"; as the very first line of the JavaScript. Predict which sloppy lines change, then run it.

  2. 2

    Lose this from a class

    Add class Box { who() { return this; } } const who = new Box().who; console.log(who()); to the first example. Predict the output without any directive: why is it strict?

  3. 3

    Shadow an id

    In the third example, add const hero = "text"; at the top. Predict what typeof hero prints now.

Code & diagrams

Silent in sloppy mode, an error in strict mode New tab

The sloppy versions "worked": one created a global `totl` by accident and one silently ignored a write. The strict versions point at the bug.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

sloppy undeclared -> number
strict undeclared -> ReferenceError: totl2 is not defined
sloppy frozen -> 1
strict frozen -> TypeError: Cannot assign to read only property 'a' of object '#<Object>'
strict new prop -> TypeError: Cannot add property b, object is not extensible
strict getter only -> TypeError: Cannot set property value of #<Object> which has only a getter
strict primitive -> TypeError: Cannot create property 'foo' on number '5'
sloppy this -> true
strict this -> undefined
Syntax strict mode forbids, and arguments and eval changes New tab

These are compile-time errors: in a real file, one of them would stop the whole script before any line ran. The Function constructor lets us show each one separately.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

SyntaxError: Strict mode code may not include a with statement
SyntaxError: Octal literals are not allowed in strict mode.
SyntaxError: Unexpected eval or arguments in strict mode
SyntaxError: Unexpected strict mode reserved word
SyntaxError: Delete of an unqualified identifier in strict mode.
SyntaxError: Duplicate parameter name not allowed in this context
arguments linked: 2 strict: 1
eval adds a variable: number strict: undefined
Legacy quirks that no mode can fix New tab

`parseInt(0.0000005)` is 5 because the number first becomes the string `"5e-7"`. Element ids becoming globals is called named access on the window object; never rely on it.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

object
true true false
false true
false
object true
1 undefined
undefined falsy true
-Infinity Infinity
function function
5

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Put the directive second

Write const x = 1; on the first line and "use strict"; on the second, then undeclared = 5;.

terminal
$ Console
── what you'll see ──
(no error: undeclared becomes a global variable)

Break #2

Add use strict to a function with default parameters

Write function f(a = 1) { "use strict"; return a; }.

terminal
$ Console
── what you'll see ──
Uncaught SyntaxError: Illegal 'use strict' directive in function with non-simple parameter list

Break #3

Concatenate a strict and a sloppy file

Bundle a.js (starts with "use strict") and b.js (sloppy code that uses with) into one file, with a.js first.

terminal
$ Console
── what you'll see ──
Uncaught SyntaxError: Strict mode code may not include a with statement

Myth vs fact

Myth

Strict mode makes code slower because it checks more.

Fact

The checks are at the same points the engine already visits, and strict code is often easier to optimise (no with, no linked arguments, no eval adding variables).

Myth

You must write "use strict" in modern code.

Fact

Modules and class bodies are always strict. Writing the directive there does nothing. It matters in classic scripts and in Node.js CommonJS files.

Myth

Strict mode fixes typeof null and == coercion.

Fact

It doesn't change values or operators. Those quirks are the same in every mode; === and a linter are the defence.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    document.all is the only object in the language with the internal slot [[IsHTMLDDA]]: typeof gives "undefined", ToBoolean gives false, and == null is true. The spec added this special case (Annex B) purely so old if (document.all) { /* IE code */ } sniffing takes the modern path.

  • ▸

    Annex B of the spec lists web-compatibility features browsers must support: __proto__, HTML comments <!-- in scripts, escape/unescape, String.prototype.substr, legacy regex syntax, and block-level function declaration hoisting rules in sloppy mode. Avoid them in new code; some aren't required outside browsers.

  • ▸

    Functions declared inside blocks behave differently by mode: in strict code they're block-scoped like let; in sloppy code Annex B also creates a var-like binding in the enclosing function. Same text, different scoping, one more reason to stay strict.

  • ▸

    Strict mode is per function, not per file at runtime: a sloppy function can call a strict one and vice versa, and each follows its own rules. Bundlers once broke libraries by hoisting a "use strict" from one module to the top of a concatenated file; ES module bundling avoids this.

Remember this

  1. 1

    Strict mode arrived in ES5 (2009). You turn it on with the directive "use strict"; as the very first statement of a script or a function (only comments may come before it). It applies to that whole script or function, including functions inside it. It is automatically on in ES modules (type="module", import/export, Topic 12.1) and inside every class body, so most modern code is already strict without saying so. Classic scripts and Hectal's JavaScript tab are sloppy unless you add the directive.

  2. 2

    Silent mistakes become errors. Assigning to an undeclared name (totl = 5 instead of total) throws a ReferenceError instead of creating a global variable. Writing to a read-only property (a frozen object, a getter-only property, a NaN-like non-writable global) or adding a property to a non-extensible object throws a TypeError instead of doing nothing. So does setting a property on a primitive ((5).foo = 1) and delete of a non-deletable property.

  3. 3

    this is safer. In a sloppy function called plainly, f(), this is the global object (window), so a method that lost its object (Topic 7.1) quietly reads and writes globals. In strict mode this is undefined in a plain call, so the bug shows up at once as Cannot read properties of undefined. Strict mode also doesn't wrap primitives: this inside fn.call(5) stays 5, not a Number object.

  4. 4

    Some syntax is forbidden in strict code, giving a SyntaxError before anything runs: the with statement, duplicate parameter names (function f(a, a)), legacy octal literals like 010 (write 0o10), octal escapes like "\08", using eval or arguments as names, and future reserved words as names (let, static, implements, interface, package, private, protected, public, yield). delete on a plain variable name is also a SyntaxError.

  5. 5

    Strict mode also changes how eval and arguments behave. A strict eval gets its own scope, so eval("var x = 1") can't add variables to the surrounding function. The arguments object no longer stays linked to the parameters (changing a doesn't change arguments[0], and the other way round), and arguments.callee throws. These changes make code easier for engines to optimise and for people to reason about.

  6. 6

    Strict mode can't fix everything, because of "don't break the web" (Topic 0.1). Quirks that stay in every mode: typeof null === "object", == coercion surprises like [0] == false and null >= 0 while null == 0 is false, NaN !== NaN, 0.1 + 0.2 !== 0.3, ASI traps after return, var hoisting and top-level var becoming a window property, elements with an id becoming global variables (window.hero), and document.all, an object that typeof reports as "undefined" and that is falsy, kept so that ancient browser-sniffing code keeps working. Use strict mode (or modules), ===, let/const and a linter, and these rarely bite.

Explain it without notes

01

What is strict mode, how do you enable it, and where is it on automatically?

02

List four behaviours that change in strict mode.

03

Why does this differ between sloppy and strict functions called without an object?

04

Name some legacy quirks strict mode doesn't fix, and why they can't be fixed.

Practice

01

Predict and explain the output of "use strict"; function show() { return typeof this; } console.log(show(), show.call(7));.

02

This sloppy code has a typo that strict mode would catch. Add the directive, run it, and fix the bug: let score = 0; function addPoint() { scor = score + 1; } addPoint(); console.log(score);

Trade-offs

  • ↔

    Strict mode catches bugs early and is the default in modern modules, but switching a large old classic-script codebase to strict can surface errors in code that "worked", so do it file by file with tests.

  • ↔

    Knowing the legacy quirks helps you read old code and answer interview puzzles, but relying on them (ID globals, == tricks) makes code fragile; prefer explicit, boring code.

Done when you can

  • I can enable strict mode and know it's automatic in modules and classes.

  • I can list what strict mode turns into errors.

  • I can explain the this difference in plain function calls.

  • I know the syntax strict mode forbids.

  • I can name legacy quirks that remain and how to avoid them.