Javascript, declarations, and atomicity

In most C-syntax languages, one expects statements to have some degree of atomic operation. In other words, one expects the following statement to either declare and initialize the variable x, or do neither:

variable x = some_function();

The above is pseudo code, but you'd imagine that in a) compiled languages it would either succeed or raise an exception/fail/halt/not compile or b) in dynamic languages, that if some_function() fails, x is not defined or declared within the enclosing scope.

(Now whether or not a language actually provides such a guarantee of atomic behavior varies, but it's a reasonable mental model for the programmer. More so if you're a LISP fan...)


Except that's not actually how it works in Javascript.

I've been writing Javascript for pretty much my entire career, but only recently did I run into a demonstration of why JS is the outlier here. And to be honest, I never really knew it behaved this way.

To demonstrate what I mean, open up node or your JS REPL of choice and try this:

> let x = window.this_does_not_exist();

This will fail (obviously), so then you should be able to do this:

x = 2;

So you'd expect the first statement to fail, and then -- maybe -- for the second to succeed in creating a global x and initializing it with the value 2.

This is not what happens. Instead, you get this:

> let x = window.this_does_not_exist();
ReferenceError: window is not defined
> x = 2;
ReferenceError: x is not defined

Odd. But then if you try to declare it (which, logically, you might be able to do...):

> let x = 1;
SyntaxError: Identifier 'x' has already been declared

So what's going on here? Well if you read the ReferenceError after the would-be-global assignment carefully, you'll notice that it says that x is not defined. It is, however, declared.

This is strange, but it's not technically a bug. It's definitely a break from the mental model that most programmers have for how this stuff works.

According to the ECMA spec (ECMA 262, § 13.3), let, var, and const declaration/initialization statements are performed in two steps. Declaration (and thus reservation of the variable name) happens during initialization of the lexical scope (barring any syntax errors in the statement), while the actual assignment does not occur until the corresponding statement is evaluated.

Now you might sort of expect that for let and var (since it's pretty much why and how hoisting happens in JS). Whether or not it's a useful behavior for let is debatable (since people don't think of let as having any hoisting), but where things get really fun is with const:

> const x = window.this_does_not_exist();
ReferenceError: window is not defined
> x;
ReferenceError: x is not defined
> x = 1;
TypeError: Assignment to constant variable.

In other words, you'll end up with a constant that is defined, but not initialized, and that you can't use for the rest of the scope.

The weirdest part is that -- unlike let and var -- you should not be able to do this at all. Because if you try a simple const declaration with no assignment, you'll find it's not allowed:

> const z;
const z;

SyntaxError: Missing initializer in const declaration

I haven't read enough to know if this is the VMs being nice to you, or if the initializer is actually required by the spec. I think it's the latter, but haven't confirmed that yet.

Either way, I would argue it's a bug in behavior, since the resulting variable is pretty demonstrably useless. I can't think of any scenario in which you'd want the constant name reserved but unable to actually point to/hold anything.

So is this wrong?

From what I can tell: not technically, no.

As mentioned earlier, ECMA 262, § 13.3 defines the behavior of let, const, and var. And technically this is consistent with it.

What it's not consistent with is the way that just about every programmer thinks about variable declaration and initialization.

So I would contend that this is not a bug in the implementations, but a bug in the spec. (But I'm also a curmudgeonly old programmer who thinks that JS is a hack that evolved into something that resembles a functional programming language, but isn't quite a good one yet. So I'm hardly a neutral party!)