The forth place in the JavaScript WTF competition goes to the amazingly large number of ways to represent lack of value. Most C-like languages have keyword null
that means “no value”. Beside null
, JavaScript also has undefined
, apparently meaning “like, totally no value”. The actual situation is even more complicated. A variable in JavaScript can be in any of the following states of no-value-ness:
- Not declared.
- Not initialized or explicitly set to
undefined
. - Explicitly set to
null
. - Temporarily dead (this is not a joke).
There is an impressive number of subtle differences between these states.
Not declared
In default, non-strict mode, not declared variables are treated as not initialized, and have implicit value of undefined
. In strict mode, accessing a not declared variable causes a ReferenceError
, except when used with typeof
.
'use strict'; console.log(typeof notDeclared==="undefined"); // prints true console.log(notDeclared===undefined); // ReferenceError
Note that typeof notDeclared
returns string "undefined"
, which is different from undefined
value.
Not initialized
Declared, but not initialized variables are implicitly assigned a value of undefined
. There is no way to distinguish between a variable that was never initialized, and a variable that was explicitly set to undefined
.
var notInitialized; console.log(typeof notInitialized==="undefined"); // true console.log(notInitialized===undefined); // true
Null
Null is interpreted as pointing to no object. It is different from undefined
in a number of ways:
var nullVar = null; console.log(typeof nullVar === "object"); // true console.log(nullVar === null); // true console.log(nullVar === undefined); // false console.log(nullVar == undefined); // true console.log(1+null); // 1 console.log(1+undefined); // NaN
An attempt to access any property of null
or undefined
leads to ReferenceError
. One can check for null
or undefined
in one shot using equality operator “==”:
if (someVar == null) ... // true if someVar is undefined or null
However, simply writing if (!someVar)
is not recommended: the condition will be true not only for null
and undefined
, but also for false
, the number 0
, the empty string ""
and Number.NaN
.
Dead
Temporarily dead variables are an addition of EcmaScript6: these are variables declared via let
or const
keywords in the lines preceding their declaration:
function foo() { // x is in a "temporarily dead zone" (TDZ) here let x; // x has value of undefined here }
“Normal” variables are “hoisted” to the top of their scope, but “let” and “const” variables are not. Any access to a let
or const
variable prior to its declaration is not allowed. This includes the typeof
operator.
console.log(declaredLater===undefined); // true console.log(typeof notDeclared==="undefined"); // true console.log(typeof dead); // ReferenceError var declaredLater = 42; let dead;
Conclusion
To be fair, there are situations when it helps to distinguish “a value of null” and “no value at all”. E.g. when sending partial updates, null
may mean “replace this field with null” and undefined
may mean “do not touch”. When dealing with default function parameters: undefined
means “use default value”, and null
means “use null”. In the old days of COM we had VT_NULL, VT_EMPTY, and also vtMissing, which actually is a third, different value.
Still, JavaScript situation is a mess. The differences between the four “no-value” states are not intuitive, to put it mildly, and there are no explicit checks for them. I wish we had something like isDeclared(symbol)
, and isInTdz(symbol)
, but alas: the best we can do is to rely on confusing side-effects. Hence, the WTF.