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. nullBoth 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 sometimes 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 nullThe 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.
undefinedUninitialized 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);
nullThe 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 nullChecking 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
The nullish coalescing operator (??) lets us use a default if a value is undefined or null:
value ?? defaultValue
value is undefined nor null, defaultValue is evaluated and the result is returned.
value is returned.
Examples:
> undefined ?? 'default'
'default'
> null ?? 'default'
'default'
> false ?? 'default'
false
> 0 ?? 'default'
0
> '' ?? 'default'
''
> {} ?? 'default'
{}
?? is short-circuiting?? is short-circuiting – the right-hand side is only evaluated if it is actually used:
let evaluated = false;
// Right-hand side is not used
123 ?? (evaluated = true);
assert.equal(evaluated, false);
// Right-hand side is used
undefined ?? (evaluated = true);
assert.equal(evaluated, true);
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)']
);
||) 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
The nullish coalescing assignment operator (??=) assigns a default if a value is undefined or null:
value ??= defaultValue
value is either undefined or null, defaultValue is evaluated and assigned to value.
Examples:
let value;
value = undefined;
value ??= 'DEFAULT';
assert.equal(
value, 'DEFAULT'
);
value = 0;
value ??= 'DEFAULT';
assert.equal(
value, 0
);
??= is short-circuitingThe following two expressions are roughly equivalent:
a ??= ba ?? (a = b)
That means that ??= is short-circuiting – the following two things only happen if a is undefined or null:
b is evaluated.
a.
let value;
value = undefined;
value ??= console.log('evaluated');
value = 0;
value ??= console.log('NOT EVALUATED');
evaluated
??= 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 .prop and returns the result.
function getProp(x) {
return x.prop;
}
If we apply getProp() to various values, we can see that it only fails for undefined and null:
> getProp(undefined)
TypeError: Cannot read properties of undefined (reading 'prop')
> getProp(null)
TypeError: Cannot read properties of null (reading 'prop')
> getProp(true)
undefined
> getProp({})
undefined
undefined and nullIn Java (which inspired many aspects of JavaScript), initialization values depend on the static type of a variable:
null.
int variables are initialized with 0.
JavaScript borrowed null and uses it where objects are expected. It means “not an object”.
However, storage locations in JavaScript (variables, properties, etc.) can hold either primitive values or objects. They need an initialization value that means “neither an object nor a primitive value”. That’s why undefined was introduced.