Strict Mode = Static Scoping
It is indeed time to start using JavaScript’s strict mode. There are many reasons, but one of the most compelling is that it brings sanity to JavaScript’s scoping rules, by guaranteeing static scoping.
Simply put, code is statically scoped if you can statically analyze it and determine what all the identifiers refer
to. In other words, you can statically determine where every variable was declared. As we’ll see, JavaScript’s sloppy
mode does not have this property, giving you yet one more reason to shun it in favor of "use strict"
.
Sloppy Mode = Dynamic Scoping
Most of the time, JavaScript scoping is fairly simple. You look up the scope chain, as declared in the source code; if you can’t find something, it must be on the global object. But in sloppy mode, there are several situations that can foil this algorithm.
Use of with
Using the with
statement completely destroys the sanity of your scope chain:
var __filename = "/my/cool/file.js";
var anotherContext = { __filename: "/another/file.js", __dirname: "/another" };
var context = Math.random() > 0.5 ? anotherContext : {};
with(context) {
console.log(__filename);
}
In this example, we can’t statically determine if console.log(__filename)
is referring to the free __filename
variable, set to "/my/cool/file.js"
, or if it’s referring to the property of anotherContext
, set to
"/another/file.js"
.
Strict mode fixes this by banning with
entirely. Thus, the above code would be a
syntax error if it were placed in a strict context.
Use of eval
Using eval
in sloppy mode will introduce new variable bindings into your scope chain:
function require(moduleId) {
// loading code elided.
}
function requireStuff() {
if (Math.random() > 0.5) {
eval("function require(things) { console.log('We require more ' + things); }");
}
require("minerals");
}
In this example, we have a similar problem as before: eval
might have dynamically introduced a new variable binding.
Thus, require
can refer either to the new function introduced by eval
into requireStuff
’s scope, or to the
function declared in the outer scope. We just can’t know, until runtime!
Strict mode fixes this by disallowing eval
from introducing new bindings. Thus,
if the above code were strict, require("minerals")
would always refer to the outer module-loading require
function.
Why Does This Matter?
In addition to the obvious implications of this for optimization and inlining in JITers, this matters because static analysis is becoming increasingly important in modern, complex JavaScript.
For example, let’s say you were writing
a tool for using Node.js-style modules in the browser. To do so, you’d
probably need to detect require
calls; in particular, if you see require
,
you’ll want to know what scope that require
comes from: global or local? Similarly, you might want to
detect references to __filename
, __dirname
, Buffer
, etc. and
make them available if they’re detected.
But in sloppy mode, it is literally unknowable what a given variable refers to. At any time, an enclosing with
or a
nearby eval
could come along, and really ruin your day. In such a setting, static analysis is doomed; as we’ve seen
in the above examples, the meaning of identifiers like require
or __filename
can’t be determined until runtime.
So? Just Don’t Use Those Things
A common refrain from people who can’t handle typing `“use strict” is that they’ll simply not use these features. And this indeed suffices: if you subset the language, perhaps using tools like JSHint to enforce your subsetting rules, you can create a more sane programming environment.
Similar arguments are applied commonly in other languages, like prohibiting the use of exceptions or templates in C++.
Even telling people to not pass expressions to their require
calls in Node.js modules falls under this category (with
the rationale that this breaks the popular browserify tool).
I don’t buy these arguments. A language should give its users built-in tools to use it correctly. In the case of
JavaScript, there is one very clear tool that has been given: a simple, backward-compatible "use strict"
pragma at the
top of your source files. If you think that’s difficult, try being a C++ programmer and writing exception-safe code: the
techniques you need to use are a lot more involved than a single pragma.
Use Strict Mode
In the words of Mark Miller, ECMAScript 5 strict mode has transitioned JavaScript into the “actually good” category of programming languages. Let’s use it. Opt in to static scoping, and a saner language in general. Use strict.
This post was inspired by a long-ago es-discuss thread, which references a talk by Mark Miller. Further clarifications were had in another, recent es-discuss thread.