Why Does This Even Matter?
Do we really need to talk about this, does it even matter? They’re both falsy values that behave extremely similarly, isn’t this a little pedantic? I don’t think so.
Declared Vs. Undeclared
undefined can be both a declared and an undeclared value. In the following example we show 2 variables with
undefined values, one is declared and the other is undeclared. The undeclared variable will throw a
ReferenceError stating that the variable is not defined, the other simply returns
undeclared variable throws an error, it still holds the same type as the
declared variable. See the following example using the above variables:
Well That’s Confusing…
Is the type situation better for a
null value, is that why I prefer it? Not exactly, it has its own issues to deal with… great! Consider the following:
That’s odd, when you check the type of
null it returns that it is an object. Does this mean
How Do We Type Check For Null?
The situation above makes things a little tricky when you want to explicitly check for a null type. Thankfully since
null isn’t really an object, it’s the only “object” that is a falsy value, empty objects are truthy. We can use this check along with a type check to check for a its type:
While a little more to write, this is the only way we can explicitly check for a declared empty value. As shown before, the
undefined type can be returned on declared and undeclared values. If you’re concerned about having to write the extra conditional, create a simple null checking function:
Isn’t This An Opinion Piece?
Yes, but I felt that a little information was needed to drive the point home, back to the original topic.
We have 2 different types that can represent empty values, which should we choose? JS already chose that for us, most issues are caused due to an object or array that evaluates to
undefined . This ends up throwing an error when attempting to access object properties or calling object/array methods. See below:
With every added level of depth, the likelihood that an error could be thrown is increased. This is because an error will be thrown if any parent property in the chain evaluates to a non-object.
As the above scenario happens fairly frequently, we need a way to differentiate empty values caused by errors and empty values that are intentionally empty. This is where
null comes into play.
Consider the following example:
This may look innocent and easy to remember, but sometimes objects are imported within multiple files. On top of this, they may be passed through multiple functions. Original values may not be immediately obvious, especially to someone unfamiliar with your codebase (freelancers & consultants).
It would be easy to do the following and assume something broke during data processing rather than the value was intentionally assigned as undefined:
If I saw the following output, I would assume this empty
null value was intentional and would not waste my time investigating a possible issue:
A Better Alternative
When possible (see Polymorphic Values), it is better to represent empty values as an empty version of its expected type. This prevents array, object, and string methods from throwing an error when they’re presented with a
Compared to assigning the same variables with null values:
The above technique works great in most situations, but sometimes we don’t have that much fine grained control of how our values are assigned. For example, a variable’s value could be set directly from the response from an API. This API could return different types depending on parameters passed or other situations outside of the developer’s control. In this situation, it’s best to set an initial value of
null and null check downstream.
EXTRA: Safely Access Deep Object Properties
A common method to safely access object properties is to use a ternary. This unfortunately becomes a little cumbersome when accessing properties that are multiple levels deep.
We can define a function to safely access object properties. If any property in the chain can not properly evaluate, it will return
null. This first argument is the object we’re attempting to access properties on. The second argument is a string of props, split by periods. We’ll first define an
isUndefined method to match our
isNull method from earlier.
Now we define our
The above example
canSafelyAccessNestedProperty can be converted using our
get function like so:
We can save a getter to a specific object by only supplying the first argument. This is useful when you need to access an object multiple times with different property chains.
Feedback? Words of encouragement? 🎉
I’m glad you made it, hopefully you learned a few patterns or pieces of information to use in the future. I’m always looking to improve my articles, if you would like to leave feedback (I would love it if you did!) you can find the Google Form here. It’s very short, I promise!