JavaScript is a beautiful language, but it has some bad parts, like any language. JavaScript's Bad Parts can be basically be broken down into two categories: Those that are confusing, and those that are dangerous. This post is specifically about one of those dangerous features.
Recently, I found a subtle bug1 in YUI where the array methods defined in NodeList would fail with edge-case values, such as 0. The bug was on a line of source reading as follows:
while (arg = arguments[i++]) {
This is a common trick, one often used in C/C++, where you can perform an assignment from the array, and test for the values 'truthiness' all in a single statement. The problem is that JavaScript has a broad definition of what is 'false' in the language. The list of 'false' values, when used in conditionalys, include: boolean false, 0, '', NaN, null, and undefined. However, there are situations where one of these 'false' values would be perfectly valid.
I ran into this bug, when working on my gallery-node-extras2 module, specifically the nextAll and prevAll methods, get all the siblings preceeding or following a node that match a given selector (or any valid argument to filter, which includes functions if you use my gallery-nodelist-extras3). In prevAll, I get all the siblings that precede the current node with the following snippet:
var list = this.ancestor().get('children'); list = list.slice(0, list.indexOf(this));
In my tests, this failed, because the call to slice was returning all the children, because the first argument, 0, is falsy, and therefore the while loop doesn't execute at all, resulting in slice being called on the NodeList with no arguments, returning a shallow copy. This bug has been fixed in git master4, but Matt Sweeney didn't take my suggested fix.
// My fix: while ((arg = arguments[i++]) !== undefined) { // What Matt Committed while (typeof(arg = arguments[i++]) !== 'undefined') {
I was a bit confused. My version was more compact, and it seemed that it should be the better solution. However, I respect Matt a lot, so I knew that there must have been something I was missing. It turns out, that in JavaScript, the value of the keyword undefined can be...redefined. So, if you set undefined to something ridiculous, say the value of Pi multiplied by the Avagadro's number5, my code would find itself in an infinite loop!
So, why is undefined allowed to be variable? I have no idea. If anyone can tell me how this came about, I'd LOVE to hear it. The 'null' keyword can't have it's value changed, so this just seems like a ridiculous oversight. If you're using this oddity of the language, stop. You're a bad person. And I don't mind saying so.
Firefox 4 has made undefined read-only, as demonstrated by the following script.
undefined = true; console.log(undefined); console.log(typeof(undefined)); test2 = function() { undefined = true; console.log(undefined); } test = function() { "use strict"; console.log(undefined); } console.log(undefined); test(); console.log(undefined); test2(); console.log(undefined); /// Outputs in Firefox 4 Beta 10 /// undefined /// undefined /// undefined /// undefined /// undefined /// undefined /// undefined /// Outputs in Chrome 9.0.597.86 /// true /// boolean /// true /// true /// true /// true /// true
The console.log statement inside of my 'strict' block is still tainted by the non-strict reassingment of undefined in Chrome. Even if you get rid of the first reassignment, calling test2()
above redefines it for test()
. Hopefully, when Chrome supports ES5 strict mode, it will protect undefined as agressively at Firefox 4. Incidentally, when in strict mode, trying to assign to undefined raises a TypeError due to it's read only nature.
Why ECMAScript 5 did not promote undefined
from what appears to be a variable into a protected keyword, like null
is (Chrome balks at attempts to assign to null
), I have no idea. Protection of undefined
should not be down to setting the read-only flag on the undefined
property of the window
object, especially when null
is already appropriately protected. This is the kind of language change that I think should have been made, because any code it may have broken isn't just bad, it is dangerous. There are already people calling for the death of JavaScript due to such poor security decisions6, a sentiment I don't necessarily agree wholesale with, but I think that the language can be made better by fixing this sort of thing. Even at the risk of breaking code.
At the very least, it would have been lovely to see this addressed in EcmaScript 5's new 'strict mode7.' Using strict mode is simple, though it's opt-in. Basically the first instruction in your JavaScript file, or a given method, must be the string "use strict";
For YUI3, which is built on the module pattern, this allows you to enable strict mode for your module easily, since your module code is wrapped either in an add
function call or a use
call. Unfortunately, strict mode is only enforced at the moment in Firefox 4, which is still in Beta.
Had this been enforced in strict mode, then we could be ensured that undefined
was actually, well, undefined
in all strict-mode scripts. We could have expected a reference error if, by some accidental idiocy we attempted to change the value of undefined
. And, there would have been reason to begin enforcement of ES5 strict mode through a page-level meta tag which could have set a flag requiring all code on the page be strict. That way, strict mode is still opt-in, but I can be notified if I've either written, or am trying to use, shit code that I should probably have some concerns with.