Showing posts with label bug. Show all posts
Showing posts with label bug. Show all posts

Tuesday, 21 September 2010

A literal improvement

JavaScript literals are getting better. Until recently, the grammar for object literals didn't explicitly allow a trailing comma, like this:

var obj = {
foo: 42,
bar: 27, // <== This is the problem
};
SpiderMonkey (Firefox), V8 (Chrome), and whatever Safari and Opera use don't care, but JScript (IE) prior to JScript 6 (IE8) throws a parsing exception on the comma and your script dies. (JScript 6 / IE8 fix this.)

A trailing comma in an array literal has a different issue:
var a = [1, 2, 3, ];
All versions of JScript so far (including JScript 6 / IE8) create an array with four (yes, four) entries, the last of which is undefined. This isn't unreasonable, because the spec wasn't explicit about it and we were always allowed to have blank entries (e.g., var a = [1, , 3];) and those entries defaulted to undefined — but everyone else went the other way and created an array with three entries instead.

Fortunately, ECMAScript 5 clears this up. The trailing comma is explicitly allowed in object literals (Section 11.1.5), and in array literals (Section 11.1.4). In the case of array literals, the trailing comma doesn't add to the length of the array (a.length above is 3).

The team behind IE9 are very engaged with standards bodies now, so hopefully that includes the JScript folks and they'll change the array behavior, though you know it must be a much harder sell for them than the object literal was — it involves changing the behavior of something that did work. Still, here's hoping.

Thursday, 16 September 2010

Double-take

There's an issue with Microsoft's JScript interpreter (the one used by IE, Windows Scripting Host, and others) that you see mentioned deep in discussions of other things. I thought it would be worth just briefly talking about on its own.

Update: The newer version of JScript used by IE9 and up doesn't have this bug anymore. Yay! Still in IE8 and earlier, though.
Basically, if you use a named function expression in your JavaScript code, JScript will process it twice, creating two separate function objects, at two separate times: First it treats it as though it were a function declaration (even though it isn't), and then it treats it as the expression it is. This is not a distinction without a difference, either, as we'll see below. (Amongst other things, it creates "symbol bleed," putting the function's name in the enclosing scope in clear violation of Section 13 of the specification, which says that it should only be defined within the function's own scope).

What do I mean by function declaration vs. function expression (named or otherwise)? Here's a function declaration:
function foo() {
}
Here's an anonymous function expression:
var foo = function() {
};
And here's a named function expression — this is the one JScript (IE) has an issue with:
var f1 = function foo() {
};
The easiest way to tell whether you have a declaration or an expression is to ask yourself: Are you using it as a right-hand value? E.g., are you assigning it to a variable/property or passing it into a function as an argument? If so, it's an expression. If it's standalone, it's a declaration. Here are some further examples of named function expressions:
bar(function foo(){});

var obj = {
nifty: function foo() {
}
};
So first off, how do we know JScript is creating two function objects? Here's the easiest way:
var f1 = function foo() {
alert(f1 === foo); // alerts "false" on IE, "true" on other browsers.
};
f1();
So, okay, but what do we care? Well, let's say you want to hook up an event handler and have it unhook itself later if some condition is met:

Prototype example:
$('foo').observe('click', function fooClickHandler() {
if (/* ...some condition... */) {
this.stopObserving('click', fooClickHandler);
}
});
jQuery example:
$('#foo').click(function fooClickHandler() {
if (/* ...some condition... */) {
$(this).unbind('click', fooClickHandler);
}
});
Perfectly reasonable, but won't work on IE. The handler will remain attached, because when you unhook a specific event handler, the function reference you give has to be the same as the reference you want to remove. On IE, the above, it isn't: fooClickHandler isn't the same function that we hooked up. The expression returned a different function.

So how do you work around it? Just make sure you're using declarations, like this:

Prototype example:
function fooClickHandler() {
if (/* ...some condition... */) {
this.stopObserving('click', fooClickHandler);
}
}
$('foo').observe('click', fooClickHandler);
jQuery example:
function fooClickHandler() {
if (/* ...some condition... */) {
$(this).unbind('click', fooClickHandler);
}
}
$('#foo').click(fooClickHandler);
If you don't want fooClickHandler to be a symbol in that scope, wrap it up in a scoping function, like so:

Prototype example:
(function() {
function fooClickHandler() {
if (/* ...some condition... */) {
this.stopObserving('click', fooClickHandler);
}
}
$('foo').observe('click', fooClickHandler);
})();
jQuery example:
(function() {
function fooClickHandler() {
if (/* ...some condition... */) {
$(this).unbind('click', fooClickHandler);
}
}
$('#foo').click(fooClickHandler);
})();
Alternately, you could just not use names (and use arguments.callee to unhook the handler), but there are lots of good reasons not to do that (arguments.callee is slow on most browsers, not allowed in ECMAScript's new "strict" mode, and besides, names are good).

So what's this "symbol bleed" issue I mentioned? Well, according to the specification, the scope of the function name in a function expression is confined to the function itself, not the encompassing scope. So:
var f1 = function foo() {
// `foo` is defined here
};
// but not here
Whereas, of course, if that were a function declaration, the foo symbol would (of course!) be defined in the scope in which the function is declared.

(You can see this coming, can't you?) Since one of the times IE processes the named function expression it treats it as a declaration, it incorrectly defines the symbol in the enclosing scope — much like we would do if we didn't use the scoping functions above — which is incorrect.

Happy coding!

Tuesday, 16 March 2010

...by any other name, would smell as sweet

Although it's not a new problem, lately I've been seeing so many people running into a specific issue with Internet Explorer that I thought it worth just jotting down what the problem is and how to get around it.

The problem, in brief, is conflation. Internet Explorer (v6 and v7) mixes together the namespaces of the id and name attributes, which should be completely distinct from one another. id, you'll recall, must be unique within the document; it uniquely identifies an element. name, on the other hand, is not for uniquely identifying things (well, not mostly). It's used for a couple of different things, such as giving form fields the names they'll have when submitted, or giving anchors a name. There's no requirement that name values be unique — in fact, when doing forms, there are lots of reasons for using the same name for multiple fields (radio buttons, for example).

Unfortunately, the Internet Explorer engine will find elements by name sometimes when it really shouldn't, specifically when you're using document.getElementById. So for instance, say you have a form in your document with a 'stuff' field:

<input type='text' name='stuff'>
and later in that same document you have a div with the id "stuff":
<div id='stuff'>...</div>
Consider this code:
var elm;
elm = document.getElementById('stuff');
if (elm) {
alert("The element's tagName is " + elm.tagName);
}
else {
alert("Couldn't find the element.");
}
In a browser that implements document.getElementById correctly, that should alert "The element's tagName is DIV". But on IE, it will alert "The element's tagName is INPUT" because it incorrectly finds the input field. The only way around this is to change the id or name of one element or the other.

Another related problem is that id values are not case-sensitive in IE6 or IE7, although of course the standard says they should be. So it'll also confuse an element with the id (or name) 'stuff' with one with the id (or name) 'Stuff'. (To be fair, surely you and I would as well?)

There's good news, though: Microsoft does document the behavior, and they've fixed it in IE8, so there's hope for the future.

Perhaps slightly OT, but lest people accuse me of Microsoft-bashing (er, I have been known...), let me just remind everyone who brought us XMLHttpRequest (the basis of Ajax) and innerHTML, both of which I use (directly or indirectly) in nearly every JavaScript-enabled browser project I do — and I bet you do too. So hey, they got many things wrong, but got some things very right as well. Also useful to remember — as we continue to bash IE6 with repeated shouts of "Die! Die!" — just how much amazingly better (faster, less crash-prone, more feature-rich) it was than its chief rival, back in the day.