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.freezeorObject.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.
"use strict";
let total = 0;
totl = 5; // ReferenceError: totl is not defined02Notice 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.
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.
"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
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
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
Shadow an id
In the third example, add
const hero = "text";at the top. Predict whattypeof heroprints now.
Code & diagrams
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
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 -> undefinedThese 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
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`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
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
5Break 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;.
Break #2
Add use strict to a function with default parameters
Write function f(a = 1) { "use strict"; return a; }.
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.
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.allis the only object in the language with the internal slot[[IsHTMLDDA]]:typeofgives"undefined",ToBooleangivesfalse, and== nullistrue. The spec added this special case (Annex B) purely so oldif (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 avar-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
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 everyclassbody, 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
Silent mistakes become errors. Assigning to an undeclared name (
totl = 5instead oftotal) throws aReferenceErrorinstead of creating a global variable. Writing to a read-only property (a frozen object, a getter-only property, aNaN-like non-writable global) or adding a property to a non-extensible object throws aTypeErrorinstead of doing nothing. So does setting a property on a primitive ((5).foo = 1) anddeleteof a non-deletable property. - 3
thisis safer. In a sloppy function called plainly,f(),thisis the global object (window), so a method that lost its object (Topic 7.1) quietly reads and writes globals. In strict modethisisundefinedin a plain call, so the bug shows up at once asCannot read properties of undefined. Strict mode also doesn't wrap primitives:thisinsidefn.call(5)stays5, not aNumberobject. - 4
Some syntax is forbidden in strict code, giving a SyntaxError before anything runs: the
withstatement, duplicate parameter names (function f(a, a)), legacy octal literals like010(write0o10), octal escapes like"\08", usingevalorargumentsas names, and future reserved words as names (let,static,implements,interface,package,private,protected,public,yield).deleteon a plain variable name is also a SyntaxError. - 5
Strict mode also changes how
evalandargumentsbehave. A strictevalgets its own scope, soeval("var x = 1")can't add variables to the surrounding function. Theargumentsobject no longer stays linked to the parameters (changingadoesn't changearguments[0], and the other way round), andarguments.calleethrows. These changes make code easier for engines to optimise and for people to reason about. - 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] == falseandnull >= 0whilenull == 0is false,NaN !== NaN,0.1 + 0.2 !== 0.3, ASI traps afterreturn,varhoisting and top-levelvarbecoming awindowproperty, elements with anidbecoming global variables (window.hero), anddocument.all, an object thattypeofreports as"undefined"and that is falsy, kept so that ancient browser-sniffing code keeps working. Use strict mode (or modules),===,let/constand a linter, and these rarely bite.
Explain it without notes
What is strict mode, how do you enable it, and where is it on automatically?
List four behaviours that change in strict mode.
Why does this differ between sloppy and strict functions called without an object?
Name some legacy quirks strict mode doesn't fix, and why they can't be fixed.
Practice
Predict and explain the output of "use strict"; function show() { return typeof this; } console.log(show(), show.call(7));.
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
thisdifference in plain function calls.I know the syntax strict mode forbids.
I can name legacy quirks that remain and how to avoid them.