undefined
and null
Many programming languages have one “non-value” called null
. It indicates that a variable does not currently point to an object – for example, when it hasn’t been initialized yet.
In contrast, JavaScript has two of them: undefined
and null
.
undefined
vs. null
Both values are very similar and often used interchangeably. How they differ is therefore subtle. The language itself makes the following distinction:
undefined
means “not initialized” (e.g., a variable) or “not existing” (e.g., a property of an object).
null
means “the intentional absence of any object value” (a quote from the language specification).
Programmers may make the following distinction:
undefined
is the non-value used by the language (when something is uninitialized, etc.).
null
means “explicitly switched off”. That is, it helps implement a type that comprises both meaningful values and a meta-value that stands for “no meaningful value”. Such a type is called option type or maybe type in functional programming.
undefined
and null
The following subsections describe where undefined
and null
appear in the language. We’ll encounter several mechanisms that are explained in more detail later in this book.
undefined
Uninitialized variable myVar
:
let myVar;
assert.equal(myVar, undefined);
Parameter x
is not provided:
function func(x) {
return x;
}
assert.equal(func(), undefined);
Property .unknownProp
is missing:
const obj = {};
assert.equal(obj.unknownProp, undefined);
If we don’t explicitly specify the result of a function via a return
statement, JavaScript returns undefined
for us:
function func() {}
assert.equal(func(), undefined);
null
The prototype of an object is either an object or, at the end of a chain of prototypes, null
. Object.prototype
does not have a prototype:
> Object.getPrototypeOf(Object.prototype)
null
If we match a regular expression (such as /a/
) against a string (such as 'x'
), we either get an object with matching data (if matching was successful) or null
(if matching failed):
> /a/.exec('x')
null
The JSON data format does not support undefined
, only null
:
> JSON.stringify({a: undefined, b: null})
'{"b":null}'
undefined
or null
Checking for either:
if (x === null) ···
if (x === undefined) ···
Does x
have a value?
if (x !== undefined && x !== null) {
// ···
}
if (x) { // truthy?
// x is neither: undefined, null, false, 0, NaN, 0n, ''
}
Is x
either undefined
or null
?
if (x === undefined || x === null) {
// ···
}
if (!x) { // falsy?
// x is: undefined, null, false, 0, NaN, 0n, ''
}
Truthy means “is true
if coerced to boolean”. Falsy means “is false
if coerced to boolean”. Both concepts are explained properly in “Falsy and truthy values” (§17.2).
??
) for default values [ES2020]
Sometimes we receive a value and only want to use it if it isn’t either null
or undefined
. Otherwise, we’d like to use a default value, as a fallback. We can do that via the nullish coalescing operator (??
):
const valueToUse = receivedValue ?? defaultValue;
The following two expressions are equivalent:
a ?? b
a !== undefined && a !== null ? a : b
The following code shows a real-world example:
function countMatches(regex, str) {
const matchResult = str.match(regex); // null or Array
return (matchResult ?? []).length;
}
assert.equal(
countMatches(/a/g, 'ababa'), 3);
assert.equal(
countMatches(/b/g, 'ababa'), 2);
assert.equal(
countMatches(/x/g, 'ababa'), 0);
If there are one or more matches for regex
inside str
, then .match()
returns an Array. If there are no matches, it unfortunately returns null
(and not the empty Array). We fix that via the ??
operator.
We also could have used optional chaining:
return matchResult?.length ?? 0;
function getTitle(fileDesc) {
return fileDesc.title ?? '(Untitled)';
}
const files = [
{path: 'index.html', title: 'Home'},
{path: 'tmp.html'},
];
assert.deepEqual(
files.map(f => getTitle(f)),
['Home', '(Untitled)']);
In some cases, destructuring can also be used for default values – for example:
function getTitle(fileDesc) {
const {title = '(Untitled)'} = fileDesc;
return title;
}
||
) for default valuesBefore ECMAScript 2020 and the nullish coalescing operator, logical Or was used for default values. That has a downside.
||
works as expected for undefined
and null
:
> undefined || 'default'
'default'
> null || 'default'
'default'
But it also returns the default for all other falsy values – for example:
> false || 'default'
'default'
> 0 || 'default'
'default'
> 0n || 'default'
'default'
> '' || 'default'
'default'
Compare that to how ??
works:
> undefined ?? 'default'
'default'
> null ?? 'default'
'default'
> false ?? 'default'
false
> 0 ?? 'default'
0
> 0n ?? 'default'
0n
> '' ?? 'default'
''
??=
) [ES2021]
??=
is a logical assignment operator. The following two expressions are roughly equivalent:
a ??= b
a ?? (a = b)
That means that ??=
is short-circuiting: The assignment is only made if a
is undefined
or null
.
??=
to add missing properties
const books = [
{
isbn: '123',
},
{
title: 'ECMAScript Language Specification',
isbn: '456',
},
];
// Add property .title where it’s missing
for (const book of books) {
book.title ??= '(Untitled)';
}
assert.deepEqual(
books,
[
{
isbn: '123',
title: '(Untitled)',
},
{
title: 'ECMAScript Language Specification',
isbn: '456',
},
]);
undefined
and null
don’t have propertiesundefined
and null
are the only two JavaScript values where we get an exception if we try to read a property. To explore this phenomenon, let’s use the following function, which reads (“gets”) property .foo
and returns the result.
function getFoo(x) {
return x.foo;
}
If we apply getFoo()
to various values, we can see that it only fails for undefined
and null
:
> getFoo(undefined)
TypeError: Cannot read properties of undefined (reading 'foo')
> getFoo(null)
TypeError: Cannot read properties of null (reading 'foo')
> getFoo(true)
undefined
> getFoo({})
undefined
undefined
and null
In Java (which inspired many aspects of JavaScript), initialization values depend on the static type of a variable:
null
.
int
variables are initialized with 0
.
In JavaScript, each variable can hold both object values and primitive values. Therefore, if null
means “not an object”, JavaScript also needs an initialization value that means “neither an object nor a primitive value”. That initialization value is undefined
.