I was just browsing the source of JSLint and noticed this piece of code:
// Is this a labeled statement?
//...
if (next_token.labeled !== true || funct === global_funct) {
stop('unexpected_label_a', label);
} //...
The interesting part is the funct === global_funct
comparison. Running the following snippet through JSLint throws an "Unexpected label" error, since the labelled statement is in the global execution context (I know, it's a stupid example. Here's a fiddle.):
loop:
for (var i = 0; i < 10; i++) {
if (i === 5) {
break loop;
}
}
If you place that same snippet in a function, JSLint is perfectly happy with it and doesn't throw an error when it encounters the label. Here's a fiddle with code that will pass JSLint. The code can be pasted into the online version of JSLint if you want to try it.
So my question: is there anything wrong with using a labelled statement in global code or is it just another personal choice by Crockford?
Following some investigation into the behaviour of labelled statements, I think this is actually just a choice by Crockford with no real basis in fact. As far as I can tell, there is no situation that could cause a naming conflict with labels in the global scope (and that seemed to be the main reason people could think of for why JSLint disallows it - see comments on the question).
The ES5 spec states the following in the section on labelled statements:
The production Identifier : Statement is evaluated by adding Identifier to the label set of Statement and then evaluating Statement.
...
Prior to the evaluation of a LabelledStatement, the contained Statement is regarded as possessing an empty label set, unless it is an IterationStatement or a SwitchStatement, in which case it is regarded as possessing a label set consisting of the single element,
empty
.
I take this to mean that every statement has a label set. Label identifiers are independant of variable and function identifiers, so it's syntactically acceptable to have a label with the same identifier as a variable in the same scope. In other words, this is valid:
var label = "My Label";
label:
for (var x = 1; x < 10; x++) {
break label;
}
Since each statement has its own label set, this is also valid:
label:
for (var x = 1; x < 10; x++) {
//Looks for 'label' in label set of `break` statement, then `for` statement
break label;
}
label:
for (var y = 5; y < 15; y++) {
//Same again. Will never look for label outside the enclosing `for` statement
break label;
}
Since you can label any statement (it's pointless, but it's possible), you can label a labelled statement:
another:
label:
for (var y = 5; y < 15; y++) {
break label;
}
When this is the case, the spec states the following:
If the LabelledStatement itself has a non-empty label set, these labels are also added to the label set of Statement before evaluating it.
In the above snippet, the label set of the for
statement contains two labels (another
and label
). It is possible to break to either of those labels from within the for
statement.
And finally, the spec also states (emphasis added):
Labelled statements are only used in conjunction with labelled
break
andcontinue
statements. ECMAScript has nogoto
statement.
So based on all that, I cannot think of a possible way for any labels in global code to interfere with other global code. Of course, it's highly unlikely you would want a program that contains multiple labels with the same identifier, and JSLint already prevents that by throwing a "label is already defined" error. But I don't think there should be any difference with how it treats labelled statements in the global execution context.