The undefined primitive type expresses that something hasn't been defined yet or remains undefined. Like null, it is a type with only one value (undefined). Unlike null, an undefined value should not be explicitly set, but may be returned by the language when something does not have a value:
const coffee = {
type: 'Flat White',
shots: 2
};
coffee.name; // => undefined
coffee.type; // => "Flat White"
Undefined is best thought of as the absence of something. If you ever find yourself wishing to explicitly set something to undefined, you should probably reach for null instead.
It's important to distinguish between the concepts of undefined and not even declared. In JavaScript, if you try to evaluate an identifier that does not exist within your scope, you will get a ReferenceError:
thisDoesNotExist; // !! ReferenceError: thisDoesNotExist is not defined
However, as you've already seen, if you try to evaluate a property of an object and the property does not exist, you will get no such error. Instead, it will evaluate to undefined:
const obj = {};
obj.foo; // => undefined
However, if you try to access a property under the non-existent foo property, you'll receive a TypeError complaining that it cannot read a property that has an undefined value:
obj.foo.baz; // !! TypeError: Cannot read property 'baz' of undefined
This behavior is an extension of the fact that seeking to access any property on an undefined or null value will always throw such a TypeError:
(undefined).foo; // !! TypeError: Cannot read property 'foo' of undefined
Curiously, the undefined value, unlike null, is not a literal, but is a globally available value provided by the language. Overwriting this global value is not possible in ECMAScript 2015 onward, but it is still possible to define your own value for the undefined identifier in local (non-global) scopes:
undefined; // => undefined
function weird() {
let undefined = 1;
undefined; // => 1
}
This is an anti-pattern as it can create very awkward and unexpected results. The accidental setting of undefined in a scope higher than your scope can mean that, if you were to rely on the value directly, you may end up referring to a value other than undefined. This lack of trust in the undefined value has historically meant that people have found other ways to forcefully make undefined available in their scope. For example, declaring a variable but not assigning it will always result in its value being undefined:
function scopeWithReliableUndefined() {
let undefined;
undefined; // => undefined
}
You can also use JavaScript's void operator on any value that will always return the real undefined value:
void 0; // => undefined
void null; // => undefined
void undefined; // => undefined
Explicitly setting undefined within your scope means that you can safely refer to your undefined value without worrying that it has been compromised. Fortunately, however, you can avoid the pain of having to worry about this risk by using the typeof operator:
if (typeof myValue === 'undefined') { ... }
This will not throw a ReferenceError even if myValue does not exist. The typeof operator, as we've discovered with null, is a bit of a fair-weather friend as we can't always rely on it, but it is nonetheless very useful when explicitly checking for undefined.
In summary, undefined can be used cleanly if you remember the following two points:
- Avoid directly assigning undefined to a variable; you should use null instead
- Always check for undefined explicitly, preferring the typeof operator
This concludes our exploration of primitive types in JavaScript. Now, we'll move on to non-primitives, that is, objects.