Let’s Get Explicit!

A reader asked me yesterday if there was a way to detect “at compile time” (that is, before the code runs) whether a JScript program contained misspelled variables. I’m
sure we’ve all experienced the pain of

var inveigledFroboznicator = new Froboznicator();
print(inviegledFroboznicator.frabness);

The sooner bugs can be caught, the better, obviously. We catch bugs like missing braces and unterminated strings before the script even runs, so why can’t we catch use of undeclared identifiers? Doesn’t VBScript do that with Option Explicit?

Actually, no, it doesn’t. Visual Basic detects undeclared identifiers at compile time, but VBScript does not catch them until runtime. The reason is because of the way the browser name lookup rules work. Specifically:

  • The window object is an expando object. You can add new properties to it at runtime.
  • All globally scoped variables and methods in a script block are automatically aggregated onto the window expando.

Aside: A little-known fact is that VBScript allows you to get around the second rule, though why you’d want to is beyond me. It is legal to put the Private keyword on a global declaration; such declarations will not be subsumed onto the window object.


So far there’s nothing stopping us from finding undeclared identifiers at compile time, but then we add:

  • The window object’s top-level members are implicitly visible.

And now our dream of compile time analysis vanishes. Any identifier can be added to window at any time by another script block and therefore any identifier is potentially valid in every script block. Add in the fact that the expando can be expanded with a string, and you end up with situations in which no amount of compile time analysis can possibly determine whether an identifier is legal or not. Here’s a really silly example. Suppose we have a web page with two script blogs, one JavaScript, one VBScript. The JavaScript block is:

function dothething()
{  
  window[NewVar] = 123;
}

And the VBScript block is

Option Explicit
Dim NewVar
NewVar = InputBox("Type in a word")
' type in "blah"
dothething()
window.alert blah

This creates a new property on the window object which is not known until the user decides what to type. Since the property is accessible without the window. prefix, there’s no way to know whether the blah identifier is legal until the program actually runs.

Notice that the JavaScript block uses the variable declared in VBScript — it is part of the window object because it is a global, in keeping with our rules laid out above.

Visual Basic proper does not allow access to top-level members of an object without qualification, so it knows that when it sees an undeclared variable, it really is a mistake. (Which reminds me — I wanted to tell you guys about ways to misuse the with block, but that’s another post.)

I suppose that we could have added a feature to JScript like VBScript’s Option Explicit
which catches this problem at run time, but we didn’t. As I mentioned on September 22nd, JScript already throws a runtime error when fetching an undeclared variable. There is still a potential bug due to misspelling on a variable set, so be careful out there.

Also, as Peter reminded me, the JScript .NET compiler in compatibility mode can be used to detect things like undeclared variables, provided that you’re willing to have cases like the one above show up as false positives.

One more thing before I actually go do some real work: an interesting semantic difference between Visual Basic and VBScript is caused by the fact that the declaration check is put off until run time. Because an undeclared variable causes a run time error, On Error Resume Next hides undeclared variables! So again, be careful out there! Don’t
rely on Option Explicitto save you next time you type with mittens on, because if you suppress errors, guess what? Errors are suppressed!


Commentary from 2019:

As I’ve frequently mentioned, the attitude that “the compiler is standing between me and productivity, and therefore should do what I mean” and the attitude “the compiler is my friend who is telling me about my likely mistakes” are opposites, but both are reasonable given the sort of programming you want to do.

In the 1990s we designed JavaScript and VBScript to be “do what I mean” languages, but did not anticipate that someday JavaScript would be used to write huge programs constructed by large teams. The price you pay today for the flexibility of JavaScript is the amount of testing you have to do to ensure correctness; we live in a world where even modern JS type checkers do not guarantee that they’ll find all type errors.

 

For-in Revisited

A while back I was discussing the differences between VBScript’s For-Each and JScript’s for-in loops.

A coworker asked me today whether there was any way to control the order in which the for-in loop enumerates the properties. They wanted to get the list in alphabetical order for some reason.

Unfortunately, we don’t support that. The specification says (ECMA 262 Revision 3 section 12.6.4):

The mechanics of enumerating the properties is implementation dependent. The order of enumeration is defined by the object. Properties of the object being enumerated may be deleted during enumeration. If a property that has not yet been visited during enumeration is deleted, then it will not be visited. If new properties are added to the object being enumerated during enumeration, the newly added properties are not guaranteed to be visited in the active enumeration.

Enumerating the properties of an object includes enumerating properties of its prototype, and the prototype of the prototype, and so on, recursively; but a property of a prototype is not enumerated if it is “shadowed” because some previous object in the prototype chain has a property with the same name.

Our implementation enumerates the properties in the order that they were added. This also implies that properties added during the enumeration will be enumerated.

If you want to sort the keys then you’ll have to do it the hard way — which, fortunately, is not that hard. Enumerate them in by-added order, add each to an array, and sort that array.

var myTable = new Object(); 
myTable["blah"] = 123; 
myTable ["abc"] = 456 
myTable [1234] = 789; 
myTable ["def"] = 346; 
myTable [345] = 566; 
var keyList = new Array(); 
for(var prop in myTable) 
      keyList.push(prop); 
keyList.sort(); 
for(var index = 0 ; index < keyList.length ; ++index)
      print(keyList[index] + " : " + myTable[keyList[index]]); 

This has the perhaps unfortunate property that it sorts numbers in alphabetical, not numeric order. If that bothers you, then you can always pass a comparator to the sort method that sorts numbers however you’d like.


Commentary from 2019:

A reader asked whether it was “tedious” to solve the problem that I mentioned in the final paragraph: that numbers are sorted as though they were strings, not numbers. It’s not too hard. The comparison will get strings; see if the operands parse as strings, and if so, come up with some rule. Say, any number is smaller than any non-number, numbers compare as numbers, and non-numbers compare as strings.

This is another example of API design decisions that have a negative impact on developers who have reasonable needs. Every platform should come with a “smart sort” algorithm that knows how to deal with strings that have numbers in them. It’s a slightly tricky algorithm, so let’s have experts write it once, test it heavily, and then line-of-business developers don’t need to try to re-invent it.

A Little More on Nothing

VBScript has Null, Empty and Nothing. What about JScript? Unfortunately, JScript is a little screwed up here.

Like VBScript, in JScript an uninitialized variable is a VT_EMPTY variant. To check to see if a variable is undefined, you can say

typeof(x) == "undefined"

There is a built-in Empty constant in VBScript, but not in JScript.

If you say

var x = null;
print(typeof(x));

then you get object, so you might expect that null is the JScript equivalent of Nothing — an object reference that refers to no object. From a logical perspective, you’d be right. Unfortunately, for reasons which are lost to the mists of time, JScript implements a null variable internally the same way that VBScript implements a Null variable — VT_NULL, the “data is missing” value.

This poor decision leads to two problems. First, JScript does not interoperate very well with COM objects which have methods that can legally take a Nothing but not a Null. (I believe that Peter Torr has some addon code that adds this capability to JScript.) Second, JScript processes Null variants passed into it as though they were object references, not database nulls. Thus JScript does not implement any of the rules about null propagation that VBScript implements.

We corrected these problems somewhat in JScript .NET. JScript .NET uses a null object reference to internally represent null, and can manipulate DBNull objects, so the interop problems go away. However, JScript .NET still does not implement the null propagation semantics for mathematical operations on database nulls.


Commentary from 2019:

Reader response to this article was pretty positive, with several readers noting that getting the insider perspective on the implementation decision of programming language minutia was valuable to them. As I’ve said many times before, getting that sort of information available to the public was my whole reason for starting this blog, so I’m glad to have succeeded.

One reader requested a JS linter; recall that this was still 2003 and we did not have the sort of JavaScript infrastructure available that we did today. Peter Torr noted that you could use the JScript.NET compiler as a linter, as its error detection was pretty good. And I noted that I’d be posting more about language and browser design decisions that make linting harder.