They tried to cover this up. In designing ECMAScript 2015 (a.k.a. ES6, a.k.a. ES2015), the authors identified a number of undesirable side effects of their work. “Why worry?” they asked. “People will be so smitten with arrow functions and block-scope bindings that they won’t care about a few measly backwards-breaking changes.” Well I care, and I have evidence that suggests I’m not alone.
Through an extensive survey of JavaScript across the web, I have found a file that demonstrates these hazardous changes to the language. This code, published in 2009 to a pop singer’s fan site, describes a program that is 100% compliant with the ECMAScript 5 standard. The web site will not function as intended for much longer–indeed, many of today’s JavaScript engines have already implemented new language features that interfere with its original behavior.
Needless to say, this is the least glamorous aspect of the new language, and I’m sure there are plenty of people who would rather it go unnoticed. Publicizing this information won’t earn me any friends, but I trust that history will judge me favorably.
I’ll include the source in its entirety then step through piece-by-piece to call out the increasingly-broken aspects. Finally (and most importantly) I’ll share some thoughts on what all this means for the web as a platform.
The File
$(document).ready(function() {
// set the date for midnight according to the user's clock
var LastSongRelease = new Date("2010-03-31T00:00:00");
LastSongRelease.setTime(
LastSongRelease.getTimezoneOffset() * 60 * 1000 + LastSongRelease
);
// "let" for "L.ast Song E.xclamation T.ext"
var let = [];
var first = 0, second = 1, third = 2;
{
let[first] = "omg it's out";
let[second] = "in theaters now";
let[third] = "the wait is over";
}
let = let.join("?").toUpperCase().split("?");
var SlideShow = Object.getOwnPropertyDescriptor({
get ctor() {
var $slides = $("#slides .miley-photo");
var current = 0;
this.go = function(change) {
current = (current + change) % $slides.length;
for (var i = 0 in $slides.toArray()) {
if (i === current === delete function() {}.length) {
$slides.eq(i).hide();
} else {
$slides.eq(i).show();
}
}
};
}
}, "ctor").get;
var slideShow = new SlideShow();
$("div#next-pic").bind("click", function() { slideShow.go(1); });
$("div#prev-pic").bind("click", function() { slideShow.go(-1); });
// update the countdown timer
setInterval(function() {
var milliseconds = LastSongRelease - Date.now();
var oldContent = $("#countdown").html();
var newContent = milliseconds < Number.prototype ?
"<b>" + let[-milliseconds % 3] + "!!!</b>" :
milliseconds + " milliseconds until <i>The Last Song</i> comes out!";
// only update if the text has changed
var updateDetector = "({ get " + JSON.stringify(oldContent) +
"() {}, get " + JSON.stringify(newContent) + "() {} })";
try {
eval(updateDetector);
} catch(err) {
var err;
if (!(err instanceof SyntaxError)) {
throw err;
}
return;
}
$("#countdown").html(newContent);
}, 500);
});
Explanations
It’s just 63 lines of code, but I count nine distinct features that will break in ES6. Let’s get started.
// set the date for midnight according to the user's clock
var LastSongRelease = new Date("2010-03-31T00:00:00");
LastSongRelease.setTime(
LastSongRelease.getTimezoneOffset() * 60 * 1000 + LastSongRelease
);
Here, a Date instance is being created from a ISO 8601-formatted string. Crucially, no time zone offset is specified. According to ES5, “The value of an absent time zone offset is ‘Z’.” That’s why the author needed to explicitly correct for the local clock as configured on the user’s computer. ES6, on the other hand, states, “If the time zone offset is absent, the date-time is interpreted as a local time.” In compliant engines, the “correction” is unnecessary, making the target time incorrect. This means any Date initialized to compensate for the user’s local time zone in this way will be wrong.
var let = [];
var first = 0, second = 1, third = 2;
{
let[first] = "omg it's out";
let[second] = "in theaters now";
let[third] = "the wait is over";
}
In ES5, let
is a valid identifier outside of strict
mode. While it remains a valid identifier in
ES6,
the new destructuring
assignmentsyntax takes precedence here. ES6 interprets the token sequence let [
as the
beginning of a destructuring assignment. Instead of defining numeric properties
on the array stored in let
, the final three statements will define new
block-scoped bindings for first
, second
, and third
(using the values"o"
, "i"
, and "t"
respectively).
let = let.join("?").toUpperCase().split("?");
Note the use of a Deseret character (specifically: lower case “long I”) as a
string separator. In order to restore the original array, this snippet relies
on the fact that ES5’s String.prototype.toUpperCase
does not honor surrogate
pairs, so the transformed string still
contains the same Deseret characters. ES6, on the other hand, requires that
implementors derive the result according to case
mappings.
The transformed string will no longer contain any instances of lower case “long
I”, and the resultant array will only have a single element: a string
representing the original three values joined by an upper case version of the
separator.
I’ll admit: the Deseret lower case “long I” is a somewhat uncommon choice for a separator, but the implications of this change are no less disturbing.
var SlideShow = Object.getOwnPropertyDescriptor({
get ctor() {
// (we'll get to this code in a moment)
}
}, "ctor").get;
var slideShow = new SlideShow();
In ES5, functions declared as “accessor properties” like these are just normal
functions. In ES6, they are special “method”
functionsthat do not define a prototype and cannot be used as a constructor. The
definition of SlideShow
is not in itself threatened, but ES6 environments
will generate an error when that function is invoked with new
.
That’s right, all code written in this way will suddenly produce new runtime errors! Sure, I’ve never in my life seen a constructor defined like this, but who am I to judge?
for (var i = 0 in slides.toArray()) {
// (we'll get to this code in a moment)
}
Although ES5 allows for an Initialiser to be used
here, the semantics of the for..in
statement
make it completely unnecessary. ES6 defines the grammar more strictly, so the
same code will be rejected by compliant
parsers.
Picture the surprise of the author of this code when this stops working! Well,
okay, with this one they will probably be more surprised that it worked in the
first place. There’s more, though!
if (i === current === delete function() {}.length) {
$slides.eq(i).hide();
} else {
$slides.eq(i).show();
}
ES5 states that the length
property of Function instances is not
configurable, so the delete
operation returnsfalse
and the branch is only executed when i
and current
are notstrictly equal. That property is in fact configurable in
ES6,
so the behavior of this code is inverted–the “current” image will be hidden,
and all others will be displayed.
Across the web, code that uses the expression delete function() {}.length
instead of the literal false
will suddenly start behaving in surprising ways.
This honestly seemed more catastrophic when I first started writing this post.
var newContent = milliseconds < Number.prototype ?
"<b>" + let[-milliseconds % 3] + "!!!</b>" :
milliseconds + " milliseconds until <i>The Last Song</i> comes out!";
This code depends on the fact that ES5 defines the Number prototype as “a
Number object […] whose value is +0”. Useful
as that may be, it is no longer true in ES6. The spec is very clear on this:
“The Number prototype object is an ordinary object. It is not a Number
instance”.
In an ES6 engine, the condition in the above ternary will always evaluate tofalse
, meaning (now that The Last Song has been released) the countdown
timer will be updated with an increasingly large negative value.
Can you imagine the confusion of all those visitors when the countdown starts
counting negative time? All because the developer innocently usedNumber.prototype
instead of… well, instead of 0
, which I suppose might be
the more natural/common choice here.
// only update if the text has changed
var updateDetector = "({ get " + JSON.stringify(oldContent) +
"() {}, get " + JSON.stringify(newContent) + "() {} })";
try {
eval(updateDetector);
} catch(err) {
// (we'll get to this code in a moment)
}
While ES5 explicitly forbids duplicate property names in object initializers, ES6 makes no such restriction.
Now here is a truly hazardous change! I mean, is it really wise to break the untold millions of scripts that compare strings by evaluating carefully-escaped representations inserted into an object literal definition and dynamically evaluated, branching on the presence of a SyntaxError?
Written out like that, this incompatibility doesn’t seem so bad. Let’s just forget this one.
try {
// (see above)
} catch(err) {
var err;
if (!(err instanceof SyntaxError)) {
throw err;
}
return;
}
The semantics for catch
blocks in ECMAScript
5 are a little tricky. ES5 first copies the
surrounding lexical environment, and it then inserts a new binding for the
identifier specified within the parenthesis that follow the catch
token.
Notably, ES5 makes no special restrictions on variable declarations within thecatch
block itself. ES6, on the other hand, explicitly forbids re-defining
the binding for the “caught”
error.
This snippet, which runs just fine in Internet Explorer 8, will not parse in
ES6-compliant engines.
It probably goes without saying that the var
declaration is totally
unnecessary in this case. I’m going to level with you: I’m having second
thoughts about the severity of all these conflicts.
Reflections
Okay, so maybe I laid it on a bit thick in the intro. Given how bizarre most of these examples are, it’s difficult to argue for the severity of any of the incompatibilities. And I don’t mean “bizarre” in the hoity-toity “I’m a professional developer with standards” sense, either. I mean it in the completely sincere, “I didn’t know some of those things were even possible” sense.
To be clear: the Nightmarefile doesn’t demonstrate all the backwards-breaking changes in ES6. The spec helpfully documents every intentional incompatibility under sections labeled Annex Dand Annex E. I think you’ll agree that the other incompatibilities are even more esoteric (and consequently less troubling) than those described above. Annex D and E omit the rationale for the changes (one document can only have so much information, after all), so you may find yourself asking, “Why?” Lucky for us, TC39’s decision making process is publicly documented, so you can usually find rationale for any given design decision (it just might take some digging). For insight into the process, check out the TC39 Meeting Minutes, the ECMAScript bug tracker and the ES-Discuss mailing list archives.
Considering the breadth of the changes, it’s actually amazing that so little has been broken. This is no accident; early in the discussion for ES6, TC39 member Dave Herman coined the term “One JavaScript” to signify the committee’s aversion to “breaking the web.” Every new feature has been vetted against the ES5 specification, and as you can see, only in the rarest of circumstances has existing code been threatened. No news is good news in this case, and TC39 deserves recognition for the extra care this detail requires.
Comments
We moved off of Disqus for data privacy and consent concerns, and are currently searching for a new commenting tool.
I like this, but the author missed a great opportunity to piss off more people by sticking to his guns and penning a tongue-in-cheek satire. I demand a rewrite.
You start with \”hazardous changes to the language\” and end with \”only in the rarest of circumstances has existing code been threatened\”
In other words: clickbait.
paging nathan poe to the nearest white courtesy phone, please; nathan poe to the white courtesy phone
In case you didn’t get it: https://en.wikipedia.org/wi…
This was satire, not clickbait.
Mike, thanks for the article.
Yet, I have to ask. Have you made it up on purpose? There are a number of a very contrived cases. Like naming your entities with a ‘future reserved words’ according to ECMA-262, Edition 5.1 (2011); like picking a hell of a separator character (it doesn’t even shows on my Chrome); like having a `var` inside a `catch`-block (for the caught exception’s namesake)..
You exhibit pieces of bad code (bad practices) and say it will break. Then let it break indeed! The sooner the better. As if we didn’t have enough of these ‘coders’ that produce low-quality code in steady volumes and in hopes it will hold up. Nay! Let it not.
Did you read the Reflections section? He addresses this.
This is beautiful. Really! Anyone who read this just line-by-lined nine arcane language changes and had a chance to have fun with it!
As for the charges of poor satire, satire is not the only wit. Our author played the fool, and bowed at the end.
I wish files would require a flag to activate ES20*, like ‘use strict’, but ‘use es2015’.
In that case, you should check out \”One JavaScript: avoiding versioning in ECMAScript 6\” by Axel Rauschmayer:
http://www.2ality.com/2014/…
I’ve spend fare amount of time investigating about ES2015, I’ve spent the last few month writing meteor code, life was beautiful using React + Meteor + SemanticUI, but now Meteor guys changed lot’s of things because of the ES2015, looking around ugly arrow functions, Programming is not beautiful anymore, Using arrow functions just for the sake of a ‘return’ or ‘this’ ? Maybe meteor guys should look at ‘The E Myth’ first, An enterprise shouldn’t now rely too much on complex work, I don’t think people can’t save much time by just skipping some ‘function’ keyword, If saving time is the real issue, why not just use vim, i’m sure it saves a lot by saving mouse click time; Ada isn’t much popular language as C++, because it’s more complicated than C++, Now people can find lot’s of js libraries in github, while can’t find much C++ ones, because js was easy to learn, flexible. I don’t find much useful feature from ES2015 except ‘import,export’s, It seems many people are learning ES2015 only because it’s standard, saying living on future, I have to say, great architects DO NOT rely on new ‘exciting’ technologies, but the ‘PROVEN’ ones. ES2015(ES6) is still new , not ‘Proven rock solid’ yet. I agree with the author, after struggling 20 years, JS still seems young. Some times I have the feeling, we are using js only because it’s the only choice with browsers, making cordova possible, Not because it’s awsome.
Maybe my logic is a little bit confusing here, anyway , After trying out ES2015, I remembered the time, when Borland abandoned my favorate editor C++Builder, it was really fun. But people were using VC++, only because it’s from Microsoft. Just like 20% of the population earns 80% of the wealth, 20% of the people will look into the so called ‘sure’ problems with hasitation.