Skip to content

Instantly share code, notes, and snippets.

@david-mark
Last active December 24, 2024 02:07
Show Gist options
  • Save david-mark/7a06527c5e9da908ae50a73b60ddb57f to your computer and use it in GitHub Desktop.
Save david-mark/7a06527c5e9da908ae50a73b60ddb57f to your computer and use it in GitHub Desktop.
'use strict' Considered Pointless and Harmful

'use strict' Considered Pointless and Harmful

What is strict mode?

According to the MDN reference, it is:

ECMAScript 5's strict mode is a way to opt in to a restricted variant of JavaScript [sic].

It goes on to say:

Strict mode isn't just a subset: it intentionally has different semantics from normal code.

And why should we want to opt-in to these different semantics? One huge (though becoming outdated) reason we shouldn't want the 'use strict' is stated in the very next sentence:

Browsers not supporting strict mode will run strict mode code with different behavior from browsers that do...

The reference goes on to state:

Strict mode code and non-strict mode code can coexist, so scripts can opt into strict mode incrementally.

Fine so far, but due to the proclivity of loading, mashing up and (often later) concatenating modules, we can rule out putting the directive in the global context. So let's look at what it can do for our functions, where it seems to pop up everywhere like an aggressive weed. The question is whether it is actually useful to send these strings to browsers or if this particularly popular weed is virulent.

What Can Strict Mode Do for Us?

The reference continues:

First, strict mode eliminates some JavaScript [sic] silent errors by changing them to throw errors.

Should be clear that throwing such exceptions is only useful for debugging. Based on this first point, sending scripts sprinkled with 'use strict' to our users is pointless. And as strict mode changes far more than this, it would seem like a poor idea to include the directive during development and testing only to remove it on release (more on that later).

Second, strict mode fixes mistakes that make it difficult for JavaScript [sic] engines to perform optimizations: strict mode code can sometimes be made to run faster than identical code that's not strict mode.

Now there's a vague promise. Will reserve judgment until after we've looked at the mistakes and how it fixes them. If such performance increases exist, they could bolster an argument for including the directive in release code. However, there is also the possibility that identical code could run slower in strict mode. As we'll see, this will likely be the more common case.

Third, strict mode prohibits some syntax likely to be defined in future versions of ECMAScript.

So it will cause syntax errors, which could only be useful during development. It's starting to sound like a runtime lint, which would certainly be less useful than a lint tool that points out such mistakes at build time. The point is minor for parse errors, but throwing exceptions at runtime is clearly inferior to finding the mistakes in advance.

Before moving on to the specific promises and how they are fulfilled, consider that the reference confirms that the directive is a non-starter in the global context.

...it isn't possible to blindly concatenate non-conflicting scripts.

We would have a big problem if we couldn't blindly concatenate scripts, particularly after development and debugging, which is when it is most commonly done. As a side note, this is another reminder that concatenating modules is best done offline and before development, rather than through script loaders and their "compilers". It doesn't make a lot of sense to debug code and then put it through a wringer that could change its behavior before turning over to QA or the users.

The reference suggests this alternative approach, which is commonly found in the wild:

You can also take the approach of wrapping the entire contents of a script in a function and having that outer function use strict mode. This eliminates the concatenation problem but it means that you have to explicitly export any global variables out of the function scope.

As we'll see, strict mode aspires to restrict access to the global object; assuming it is successful, there are only two options available for such exporting. One is to declare global variables, which seems to have gone completely out of style due to the use of script loaders and the associated "compilers" that sometimes wrap modules in additional one-off function calls. Should be clear that failing to declare global variables can lead to major problems, particularly for libraries that aspire to be cross-platform (i.e. run in NodeJS as well as browsers). The other option is to use the window host object as a global object surrogate, but that leads to similar problems and is clearly a non-starter as host objects are not part of the language (ECMA specifications indicate only that they are implementation-dependent).

As we'll see later in the security section, there is a third possibility as strict mode is easily circumvented to un-restrict access to the global object, simply by passing a reference to the function (or through the use of call, bind or apply). This possibility is also often undermined by meddling script loaders and their "compilers". This is why window pops up so often when libraries create their global "namespace" objects.

Ignorance has likely played a part in making the window host object such a popular surrogate as well. Have often heard the myth that strict mode restricts access to the global object, even in the global context (what a concept). Certainly raises the question of how we would ever gain access to it in a strict mode script. But regardless of how it is done, adding properties to the global object instead of declaring global variables is a poor practice that has been known to lead to obscure issues and obfuscation leading mass confusion. Here is a quote from a misinformed StackOverflow post mentioned in the previous link:

This won't work in strict mode, though, because in strict mode global code, this doesn't have a reference to the global object (it has the value undefined instead).

Obviously false and indicative of the sort of confusion that strict mode has caused; but I digress.

Clearly the use of strict mode encourages declaring global variables; but that was always the best practice and as developers more often resort to host object references to bypass this restriction, it can hardly be notched in the strict mode victory column.

So far, not very compelling, but let's move on to the specifics.

Mitigating Mistakes

The first section is about turning mistakes and previously silent failures into errors.

Implied Globals

First, strict mode makes it impossible to accidentally create global variables. In normal JavaScript [sic] mistyping a variable in an assignment creates a new property on the global object...

JSLint and similar tools have always flagged those, which is preferable to waiting until runtime to find what are usually typos or forgotten declarations. So that strict mode feature can't be considered progress.

Non-writable Variables

Second, strict mode makes assignments which would otherwise silently fail throw an exception.

Let's turn to JSLint again:

NaN = 'test';

Bad assignment to 'NaN'.

Exactly. Again, we'd rather catch such typos before running the code. Having strict mode throw an exception is not nearly as helpful.

Another example does demonstrate a win over JSLint, though it could be chalked up as more of a failing of that particular tool than a success for strict mode.

var obj1 = {};
Object.defineProperty(obj1, "x", { value: 42, writable: false });
obj1.x = 9;

JSLint is silent on this matter. So a function that actually uses such code could benefit from strict mode during development. Clearly the resulting exception won't do the users any favors. And as we would rather not modify our code between development and release, this seems more like an argument for a better lint tool. Could also be argued that such mistakes would likely be discovered during testing and in all environments, not just those that support strict mode. For example, IE 9 supports defineProperty, but ignores 'usestrict'.

There are a couple more esoteric examples cited:

// Assignment to a getter-only property
var obj2 = { get x() { return 17; } };
obj2.x = 5; // throws a TypeError

// Assignment to a new property on a non-extensible object
var fixed = {};
Object.preventExtensions(fixed);
fixed.newProp = "ohai"; // throws a TypeError

Same results and conclusion for functions that use such code: design a better testing tool. Same issue as the previous example for IE 9 as well. If we must support IE 9, then the strict mode directive would simply cause the dreaded scenario where code seems to "work" in one browser, but fails everwhere else (e.g. on upgrading to IE 10).

Should be clear that functions that do not use non-writeable properties, getters or preventExtensions would gain none of the cited benefits. Adding it to such functions just obfuscates the code as it makes the reader stop and try to figure out why it was determined to be needed.

Unique Object Literal Property Names

Fourth, strict mode prior to Gecko 34 requires that all properties named in an object literal be unique. Normal code may duplicate property names, with the last one determining the property's value.

This one is clearly a job for JSLint.

var o = { p: 1, p: 2 };

Duplicate 'p'.

Also of note for that example:

This is no longer the case in ECMAScript 6 (bug 1041128).

Apparently ECMA thought better of this restriction for version 6 and browsers will surely patch to accomodate the standard. Clear victory for the lint, which will flag issue (normally a typo) in all environments, regardless of what ECMA thinks.

Unique Function Argument Names

Similarly, duplicate function arguments get the same treatment:

function sum(a, a, c){ // !!! syntax error
  "use strict";
  return a + b + c; // wrong if this code ran
}

And score another victory for consistency and early warnings through linting:

Redefinition of 'a' from line 0.

Octal Issues

The monotony continues with octal number literals, where are not a part of ES5, but supported by most browsers:

var o = 010;

JSLint's provides this somewhat arcane warning:

Unexpected '1' after '0'.

Should be noted that ES6 provides a way to use octals:

var a = 0o10; // ES6: Octal

However, at the time of this writing, ES6 support is spotty at best; therefore, if our script needs octals, then strict mode is simply a hindrance. Environments that do not support strict mode will run the script, while those that do will throw an exception.

Setting Properties on Primitive Values

Seventh, strict mode in ECMAScript 6 forbids setting properties on primitive values.

false.true = "";         // TypeError
(14).sailing = "home";   // TypeError
"with".you = "far away"; // TypeError

JSLint gets two out of three:

Unexpected '.'.

Unexpected '.'.

Missed the first one. That's something Crockford should fix, but hard to imagine writing that on purpose or being unfortunate enough to end up with it through a typo. Call it two victories for JSLint and a push; other lint tools may well sniff that first one out.

And that wraps up the mistake mitigation section.

Simplifying Variable Uses

Strict mode also promises to improve our life through simplifying variable uses.

Strict mode simplifies how variable names map to particular variable definitions in the code. Many compiler optimizations rely on the ability to say that variable X is stored in that location: this is critical to fully optimizing JavaScript [sic] code. JavaScript sometimes makes this basic mapping of name to variable definition in the code impossible to perform until runtime. Strict mode removes most cases where this happens, so the compiler can better optimize strict mode code.

Another very vague promise: some code in some environments may be better optimized, which ostensibly will lead to faster performance in some unspecified cases. Let's look at the details to see whether strict mode scores any points here in what looks like a blowout loss so far.

With Statements

The with statement has long been considered harmful and best-avoided. This is really just another (relatively uncommon) mistake.

var x = 17;
with (obj) // !!! syntax error
{
  // If this weren't strict mode, would this be var x, or
  // would it instead be obj.x?  It's impossible in general
  // to say without running the code, so the name can't be
  // optimized.
  x;
}

JSLint has always flagged this very bad part:

Unexpected 'with'.

Eval

Hopefully we all know that eval is evil and always be avoided.

var x = 17;
var evalX = eval("'use strict'; var x = 42; x");

JSLint doesn't like it, even in strict mode:

Unexpected 'eval'.

Deleting Plain Names

Deleting plain names is a silent failure without strict mode. Strict mode adds the usual exception; again, this is just a mistake turned into an error, not a simplification. No idea how this would help with optimization either; seems to be in the wrong section.

var x;
delete x; // !!! syntax error

JSLint says:

Expected '.' and instead saw 'x'.

A bit arcane, but leaves no doubt that something is wrong.

Making eval and arguments Simpler

We'll skip eval this time around and concentrate on the latter example.

Second, strict mode code doesn't alias properties of arguments objects created within it. In normal code within a function whose first argument is arg, setting arg also sets arguments[0], and vice versa (unless no arguments were provided or arguments[0] is deleted). arguments objects for strict mode functions store the original arguments when the function was invoked. arguments[i] does not track the value of the corresponding named argument, nor does a named argument track the value in the corresponding arguments[i]

We should file this example under poor programming practice:

function f(a) {
    a = 42;
    return [a, arguments[0]];
}

But the only alleged penalty besides readability is in the optimization. Did not expect JSLint to catch this and that was the case. Also did not expect this:

This function needs a "use strict" pragma.

How ironic for JSLint to insist that function needs strict mode. Seems it is programmed to parrot that for virtually any function:

function f() {
    console.log('Hello world!');
}

This function needs a "use strict" pragma.

Whatever. :)

As all arguments are passed by value, would consider it obfuscating to reassign their values. Similarly, for object references, consider mutating their properties to be not only obfuscating but extremely harmful. Just leave the arguments alone.

Regardless, this is the first example of an optimization that is not covered properly by JSLint. However, as it certainly could be handled by a lint tool, I'm only awarding strict mode one millionth of a point in a contest that surely seems over by now. It's a potential optimization that may be available in some environements and only in the event of what I would consider poor practice (and the absence of a lint tool capable of giving rational advice for this case).

arguments.callee

Third, arguments.callee is no longer supported. In normal code arguments.callee refers to the enclosing function. This use case is weak: simply name the enclosing function!

The arguments.callee property has always been considered fairly useless, as well as bad form; though disagree with naming the enclosing function if it is an expression. Older versions of IE have issues with named function expressions and those of us that remember the "bad old days" (or those that must continue to support those browsers) have been able to get along without them just fine (and without arguments.callee).

Was quite surprised that JSLint didn't flag that one. No reason it shouldn't and other lint tools may well notice, so doesn't seem like much of a boost for the strict mode argument. We should really know better than to use this at this late date. It's usually the first thing cited by proponents of strict mode and a single global search (or better lint tool) will suffice to ferret it out. Will award strict mode one billionth of a point for this one.

And so we come to the end of the alleged performance benefits. As we've seen most of the examples revolve around avoiding certain features and poor practices and are much better handled offline using a lint tool. Moving on to alleged security benefits.

"Securing" ECMAScript

Note the double quotes. And again:

Strict mode makes it easier to write "secure" JavaScript [sic].

Referencing the Global Object in Functions

The reference goes on to explain:

First, the value passed as this to a function in strict mode is not forced into being an object (a.k.a. "boxed"). For a normal function, this is always an object: either the provided object if called with an object-valued this; the value, boxed, if called with a Boolean, string, or number this; or the global object if called with an undefined or null this.

And so what?

Not only is automatic boxing a performance cost...

Thought we were on to security at this point, but no matter. What they are saying is that code like this is a performance hindrance:

resolve.call( undefined, value );

Okay, but interesting to note that's a line from the latest jQuery with the odd explanation of:

// Strict mode functions invoked without .call/.apply get global-object context

Seems like a typo, but it's repeated again in the same function (perhaps copied and pasted). More like: normal functions invoked without call or apply have a this object that references the global object. But I digress, just demonstrating another mess created by strict mode. Here we have code that saddles older browsers with a known performance penalty in the name of strict mode; and barring a typo, would we ever write such code in the first place?

Another billionth of a point for strict mode as such mistakes would clearly come out in the wash and would be easier to track down if the issues occurred in all, rather than some environments.

The reference goes on to explain:

...but exposing the global object in browsers is a security hazard, because the global object provides access to functionality that "secure" JavaScript [sic] environments must restrict. Thus for a strict mode function, the specified this is not boxed into an object, and if unspecified, this will be undefined:

...followed by various examples. Then we have this odd aside:

That means, among other things, that in browsers it's no longer possible to reference the window object through this inside a strict mode function.

Sure they meant the global object, as it's a really bad idea to confuse that with the window host object. Regardless, this unqualified statement is false.

function test() {
    'use strict';
    window.alert(this); // [object Window]
}

test.call(window);
test.call(this);

Yes, Chrome (and other browsers) seems to indicate that the global object is the window object, but that's simply an observation, not a rule. In any event, used it twice, with a third direct reference to window, so how is 'use strict' helping to "secure" this code?

But I digress again; just can't trust wikis, no matter how official they seem. Will have to fix that line.

Walking the Stack

Second, in strict mode it's no longer possible to "walk" the JavaScript stack via commonly-implemented extensions to ECMAScript. In normal code with these extensions, when a function fun is in the middle of being called, fun.caller is the function that most recently called fun, and fun.arguments is the arguments for that invocation of fun.

Yes! ECMAScript. Finally got it right; we are talking about ECMAScript implementations. JavaScript is the brand name for implementations provided by the owner of this wiki and was misused in seemingly every prior reference quote.

function restricted()
{
  "use strict";
  restricted.caller;    // throws a TypeError
  restricted.arguments; // throws a TypeError
}
function privilegedInvoker()
{
  return restricted();
}
privilegedInvoker();

Anyway, these are very odd cases (and language extensions to boot) that JSLint should flag (but doesn't). Once again, strict mode will only help with these rare cases during development (and in browsers that support strict mode). Once you find and throw out references to these properties, you can throw out the 'use strict' directive. It's just another case where a better lint tool would me more appropriate. Also, in twenty years of scripting, I have never encountered the use of these extensions. If I had, I'd have deleted them immediately (as opposed to adding 'use strict');

Third, arguments for strict mode functions no longer provide access to the corresponding function call's variables. In some old ECMAScript implementations arguments.caller was an object whose properties aliased variables in that function. This is a security hazard because it breaks the ability to hide privileged values via function abstraction; it also precludes most optimizations. For these reasons no recent browsers implement it. Yet because of its historical functionality, arguments.caller for a strict mode function is also a non-deletable property which throws when set or retrieved:

Emphasis mine. No recent browsers implement it and, depending on the assumed definition of "recent", it can be assumed that older browsers that may implement this hazardous extension do not support strict mode. Will skip the code sample for this as it is just more of the same. It throws exceptions in some browsers on trying to use the extension (assuming that any exist that support strict mode).

And that brings us to the end of the security measures. Once again, most of the examples revolve around avoiding certain features and poor practices and are much better handled offline using a lint tool. Like the alleged performance benefits, virtually none of this applies to production code; exceptions won't do our users a bit of good.

And so we turn to the future.

The Future of ECMAScript

Future ECMAScript versions will likely introduce new syntax, and strict mode in ECMAScript 5 applies some restrictions to ease the transition. It will be easier to make some changes if the foundations of those changes are prohibited in strict mode.

Fine.

Reserved Words

First, in strict mode a short list of identifiers become reserved keywords. These words are implements, interface, let, package, private, protected, public, static, and yield. In strict mode, then, you can't name or use variables or arguments with these names.

function package(protected){ // !!!
  var implements; // !!!

  interface: // !!!
  while (true){
    break interface; // !!!
  }

  function private() { } // !!!
}

function fun(static) { } // !!!

JSLint got every one of those (and then some):

Reserved name 'package'.

Reserved name 'protected'.

Reserved name 'implements'.

Reserved name 'interface'.

Weird loop.

'interface' is not a label.

Reserved name 'private'.

Empty block.

Reserved name 'static'.

Empty block.

Function Statements

Next up is the well-known and thoroughly evil Mozilla extension that has been aped by other browser developers and that nobody should ever use:

Second, strict mode prohibits function statements not at the top level of a script or function. In normal code in browsers, function statements are permitted "everywhere". This is not part of ES5 (or even ES3)! It's an extension with incompatible semantics in different browsers.

Bit of an odd explanation there as function statements at the top level are function declarations. Richard Cornford provided a detailed explanation of the difference (and some criticism of similarly flawed documentation) back in 2008. Some things never change.

Here is the sample case:

if (true){
  function f() { } // !!! syntax error
  f();
}

JSLint to the rescue again:

Unexpected 'function'.

Worth noting that this has nothing to do with the ES5 standard:

This prohibition isn't strict mode proper, because such function statements are an extension of basic ES5.

Thanks for that one, Mozilla. Was once famously discovered in Prototype if I recall correctly; could have been Dojo or YUI. Believe it was somewhere in this lengthy thread. Even if not in that one, Cornford's posts in there regarding popular, "mature", crowd-sourced libraries are pure gold (as always). Well worth the read as many of the lessons still apply today.

But I digress one last time as we have reached the end of our journey. Will close the analysis with this quote from the final section of the reference:

The major Browsers now implement strict mode. However, don't blindly depend on it since there still are numerous Browser versions used in the wild that only have partial support for strict mode or do not support it at all (e.g. Internet Explorer below version 10!).

Don't blindly depend on it? Certainly an understatement for something that is supposed to simplify our lives.

Conclusion

'use strict' is (mostly) pointless and most assuredly harmful in a culture intent on mashing scripts together with abandon. Can cause differences in behavior between browsers, as well as changing results before and after a simple (and usually blind) concatenation of scripts. Was one of the worst things to ever happen to browser scripting and not a fan of it in other environments either. There are better ways to deal with the issues it fixes and many of the claimed security and performance benefits fall into this category as well. It's certainly not a spell that magically makes our scripts faster or more "secure" (quotes theirs).

On a final note, the standard specifications for implementing strict mode seem to imply that it is more likely to slow our code down than to speed it up. For those pressed for time, I found a couple of good explanations on StackOverflow (of all places), along with some predictably pointless examples purporting to demonstrate the opposite.

Michael Haufe provided a more appropriate sample benchmark that seems to indicate that 'use strict' will not make our code run significantly faster (and may slow it down in some environments). Taking into account parse-time penalties, speculate that the end result is going to be worse performance overall in the majority of cases. Can only speculate as haven't seen any parsing performance tests as of yet.

@david-mark
Copy link
Author

david-mark commented Dec 3, 2016

@ljharb

Browserify is https://www.npmjs.com/package/browserify, and is what webpack was also based on - in react it's just the name of a file.

And in "UMD" I assume. And what a couple of files, particularly the one in ReactJS, which mutated right after I tried to remove some unneeded code; it's all screwed up now. :(

The way modern web dev is done is with a commonJS (not AMD, which has long since fallen out of favor and is rapidly going extinct) bundler. Commonly, babel is also used.

You don't know how glad I am to hear you say that. AMD must D-I-E. Am so sorry for inadvertently unleashing that thing on the unsuspecting Web. Was never meant to escape Dojo containment.

There is certainly lots of code in the wild that doesn't follow modern, or often any time's, best practices.

Understatement of the century. :)

However, that's not relevant when discussing what best practices should be. ES modules (with import and export) are coming, and in 5-10 years, that will be the only thing anyone uses for new code.

I get that. You are focused on the future, while this article is about the present and recent past. I just think strict mode jumped the gun on some things. I certainly don't have all the answers on how to best transition to ES6 and beyond, but I don't think 'use strict' was it and have been appalled for years by its virus-like spread by developers who clearly couldn't care less about best practices. It's become a cargo cult practice and, as such, I feel it has caused far more harm than good. Certainly it hasn't been the end of the world, but there's certainly been a lot of confusion added to an industry already suffering from an overload of cluelessness.

Note that it's not that libraries and websites don't necessarily expose things to the global object - which as you point out, is useful for debugging - it's that none of their production code accesses these as user-created globals; they're accessed via a
module import mechanism, which is usually a node-style, commonJS, synchronous require.

Yes, I understand that some libraries and sites do that. As a matter of fact, My Library did that back in 2007; Jessie-built libraries do it as well. And both of them accomplish it in the same way: by declaring their global "namespace" variables. However, most popular libraries (e.g. jQuery) and the sites that use them do not do this at all; they typically augment the window object (due to strict mode confusion I assume) with references to objects that they continue to refer to internally. Worst of both worlds. :(

I like Node and will look at commonJS again (glanced at it briefly years ago), however synchronous sounds ominous to me. That's what Dojo was before I created the "AMD" structure for it. It literally locked up the browser while each script tried to download. However, my "AMD" (define was dojo.provide) also allowed synchronous loading dependencies (as well as synchronous) using document.write. Regardless, I don't see the need to dynamically download dependencies at all, which is one reason I was irritated to see "AMD" pop up all over the Web (the other was that RequireJS was/is an incompetent implementation).

I also think that browsers have far different needs than Node and the like. If we can build a bundle of dependencies before (and optionally repeat during) development, then there's no need to worry about synchronous download of each dependency. What I can't stand is when development uses a script loader, but then everything is "compiled" at the end to eliminate multiple downloads. To me that's pure lunacy, but junk like Dojo and RequireJS have made it a very popular (ahem) strategy.

@david-mark
Copy link
Author

david-mark commented Dec 3, 2016

@ljharb

I looked at Browserify and Webpack and I think we're on the same page. I've always used very simple scripts for concatenation (even BAT files up until Node came around), but certainly those tools can help when dependencies get too deep to manage like that. Apparently none of it involves dynamically loading scripts in the browser, which eliminates most of my concerns. I do wonder about the require function and how much overhead it adds, but that's a comparatively minor concern.

In Dojo I had aimed to completely eliminate all of those require/provide calls in the build process, but do understand how such calls could be used to modularize Web apps (they definitely were not used for that in Dojo, which exposed everything as globals). That was the one thing James Burke changed when he (ahem) exported "AMD" as RequireJS.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment