Consider three cases, where both a
and k
are undefined:
if (a) console.log(1); // ReferenceError
and
var a = k || "value"; // ReferenceError
seems reasonable, but...
var a = a || "value"; // "value"
Why doesn't the last case throw a ReferenceError
? Isn't a
being referenced before it's defined?
This is because of one of var
's "features" called hoisting. Per the link:
Because variable declarations (and declarations in general) are processed before any code is executed, declaring a variable anywhere in the code is equivalent to declaring it at the top. This also means that a variable can appear to be used before it's declared. This behavior is called "hoisting", as it appears that the variable declaration is moved to the top of the function or global code. (emphasis mine)
So, for example:
console.log(a);
var a = "foo";
Instead of throwing a ReferenceError
as you might expect, since a
is referenced before it is defined, it logs undefined
. This is because, as mentioned earlier, the declaration is processed first and essentially happens at the top, which means it's the same as:
var a;
console.log(a);
a = "foo";
The same goes for functions as mentioned earlier:
function foo() {
console.log(a);
var a = "foo";
}
That's the same as:
function foo() {
var a;
console.log(a);
a = "foo";
}
To see why, look into the ECMAScript 2015 Language Specification:
13.3.2 Variable Statement
NOTE
A
var
statement declares variables that are scoped to the running execution context’s VariableEnvironment. Var variables are created when their containing Lexical Environment is instantiated and are initialized toundefined
when created.[...]
A variable defined by a
VariableDeclaration
with anInitializer
is assigned the value of itsInitializer
’sAssignmentExpression
when theVariableDeclaration
is executed, not when the variable is created. (emphasis mine)
From this, we can gather that the var
declarations are created before any code is executed (in their lexical environment) and are scoped to the containing VariableEnvironment
, which is either in the global scope, or in a function. They are initially given the value undefined
. The next part explains that the value that the var
is assigned to is the value of the right hand side when the declaration is executed, not when the variable is created.
This applies to your situation because in your example, a
is referenced like it is in the example. Using the information earlier, your code:
var a = a || "value";
Can be rewritten as:
var a;
a = a || "value";
Remember that all declarations are processed before any code is executed. The JavaScript engine sees that there's a declaration, variable a
and declares it at the top of the current function or global scope. It is then given the value undefined
. Since undefined
is falsey, a
is assigned to value
.
In contrast, your second example throws a ReferenceError
:
var a = k || "value";
It can also be rewritten as:
var a;
a = k || "value";
Now you see the problem. Since k
is never a declared anywhere, no variable with that identifier exists. That means, unlike with a
in the first example, k
is never declared and throws the ReferenceError
because it is referenced before declaration.
But how do you explain
var a = "123"; var a = a || "124"; // a = "123"
?
From the ES2015 Language Specification again:
Within the scope of any VariableEnvironment a common
BindingIdentifier
may appear in more than oneVariableDeclaration
but those declarations collective define only one variable.
To put it plainly: variables can be defined in the same function or global scope more than once, but always define the same variable. Thus, in your example:
var a = "123";
var a = a || "124";
It can be rewritten as:
var a;
a = "123";
a = a || "124";
Declaring a
in the same function or global scope again collectively only declares it once. a
is assigned to "123"
, then it is assigned to "123"
again because "123"
is truthy.
As of ES2015, you should, in my opinion, no longer use var
. It has function scoping and can cause unexpected assignments like those mentioned in the question. Instead, if you still want mutability, try using let
:
let a = a || "value";
This will throw a ReferenceError
. Even though all variables are hoisted, no matter which declarator you use (var
, let
, or const
), with let
and const
it is invalid to reference an uninitialized variable. Also, let
and const
have block scope, not function scope. This is more clear and normal regarding other languages:
function foo() {
{
var a = 3;
}
console.log(a); //logs 3
}
Versus:
function foo() {
{
let a = 3;
}
console.log(a); //ReferenceError
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With