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 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. I never really knew it behaved this way, and I never would have thought to check this behavior since... well it's not exactly intuitive.
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 I suppose it makes some sense; the statement that would have defined the variable couldn't execute, so nothing was defined. Makes sense...
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. I'd postulate that in most languages you expect the success/failure model to pretty much follow statements: either a statement works, and its side-effects occur, or it doesn't and they don't.
Not Javascript. 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 it at least is recoverable.
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. Argh!
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/intent, 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 hold anything.
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 what's documented here is consistent with it.
What it's not consistent with is the way that just about every programmer thinks about variable declaration and initialization in a C-syntax language (and even non-C-syntax languages, like Lisp-likes).
So in short I would contend that this is not a bug in the implementations, but a bug in the spec.
(But then I'm also a curmudgeonly old programmer who thinks that JS is a hack that has been beaten into something that resembles a functional programming language, but isn't really a good one yet. So I'm hardly a neutral party!)