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
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...)
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
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
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),
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
Now you might sort of expect that for
var (since it's pretty much
why and how hoisting happens in JS). Whether or not it's a useful behavior
let is debatable (since people don't think of
let as having any
hoisting), but where things get really fun is with
> 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
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.
From what I can tell: not technically, no.
As mentioned earlier, ECMA 262, § 13.3 defines the behavior of
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!)