Working with Nullability in JavaScript: Variables and Functions
Like many other programming languages, JavaScript has indicators of absence: the values null and undefined. Both null and undefined are the exclusive values of their respective data types, as we’ve discussed in Talking About Null, Undefined, Not Defined, and Undeclared. Simply put, undefined is a value produced by the system, while null is a value consciously assigned by the programmer.
Although fundamentally different, null and undefined share many similarities. Both indicate absence; both are the exclusive values of their respective data types; and both are falsy. Because of these similarities, both values are then classified as nullish values.
There are several things worth paying attention to when working with nullish values.
Nullish Values and Variables
When a variable is declared without a value, its value is undefined. While this is generally fine, it is better practice for a programmer to declare a variable with null if the variable is intentionally declared without a value.
let sesuatu; // avoid
let something = null; // prefer
Beyond readability, their type coercion behavior also differs. When performing an operation that expects a numeric value, undefined will be coerced to NaN, while null becomes 0.
let x;
let y = null;
console.log(1 + x); // NaN
console.log(1 + y); // 1
Also worth noting: while a variable declared without a value will have the value undefined, a variable that has never been declared… well, doesn’t exist at all.
If you perform a simple check with if(foo) that relies on undefined being coerced to false, you’ll get a ReferenceError if foo hasn’t been declared. In situations where you’re not sure whether the variable you want to check has been declared, the typeof operator can be used.
The typeof operator always returns a string value, even for variables that haven’t been declared — in that case, it will return "undefined".
if (foo) {
// avoid!
/* besides throwing a ReferenceError if foo
** hasn't been declared, this check also fails for
** falsy values like 0, '', null
*/
}
if (typeof foo !== "undefined") {
// prefer!
/* only fails for two cases: foo is undefined,
** or foo hasn't been declared
*/
}
Nullish Values and Functions
Functions treat null and undefined differently. If you have a function that accepts a parameter, calling it without providing an argument for that parameter will make the parameter variable’s value inside the function undefined.
function foo(val) {
console.log(val);
}
foo(); // undefined
If we think about it, the parameter variable is always declared inside the function. You won’t get a ReferenceError if you check if (val) inside that function. However, the most accurate choice for checking undefined remains using typeof val === 'undefined'.
What if our function’s parameter has a default value? This is one of those gotchas that once tripped me up. It turns out that no matter how similar null and undefined are, they are still different things — let’s take a look.
function foo(val = 42) {
return val;
}
foo(); // 42
foo(undefined); // 42
foo(null); // null
Passing null will not cause the function parameter to fall back to its default value, unlike undefined. Why is that? Simply put, it goes back to the definition of null above. The value null is a value given by the programmer to explicitly indicate the absence of a value. So the presence of null is considered intentional and does not warrant replacement with a default value.
Speaking of intentional, isn’t passing undefined as an argument also done deliberately? If not, why would we bother with foo(undefined) when foo() alone would suffice?
The answer is that undefined as an argument doesn’t always arrive there intentionally via the primitive undefined literal. Consider the example below.
const book = {
title: "Moby Dick",
author: "Herman Melville",
price: 100000,
};
function calculateFinalPrice(basePrice, discount = 0) {
return basePrice * (discount / 100);
}
function addToCart(book) {
const finalPrice = calculateFinalPrice(book.price, book.discount);
// ...
}
The call to calculateFinalPrice inside the addToCart function intentionally passes the second argument book.discount, which maps to the discount parameter in calculateFinalPrice. However, the book object doesn’t have a discount property, so its value becomes undefined. In this case, it makes perfect sense for the default value to take over the book.discount argument being passed, doesn’t it?
OK, so does calling a function without an argument mean the same as calling it with undefined? Not quite :). Calling a function with undefined as an explicit argument can be distinguished from calling a function with no arguments at all.
Every function in JavaScript has an arguments object — an Array-like object containing the arguments passed to that function. It’s called Array-like because it resembles an Array in that it has a length, but it doesn’t have all Array properties. Through the arguments.length property we can identify the difference between calling foo() and foo(undefined).
function foo() {
console.log(arguments.length);
}
foo(); // 0
foo(undefined); // 1
foo(undefined, undefined, undefined); // 3
Why does this distinction matter? Let’s illustrate. Since ES6, we can use rest parameters to represent an unlimited number of arguments passed to a function. With rest parameters, those arguments practically become an Array with all its prototype methods.
function printArgs(...args) {
args.forEach((arg) => {
console.log(arg);
});
}
printArgs(); // loop doesn't run at all
printArgs(undefined, undefined, undefined); // loop runs 3 times
This knowledge may come in handy if you’re building a function with a dynamic number of parameters, where the arguments passed to it can have undefined values.
That wraps up the overview of nullish values, null and undefined, in relation to variables and functions in JavaScript. From the discussion above, it seems like there are more differences than similarities, right? So why are null and undefined grouped together if they have so many differences?
The answer will become clear in our next discussion, Working with Nullability in JavaScript: Objects and Question Mark Operators.