Checking for the Absence of a Value in JavaScript
When I first started learning JavaScript I was confused by the seemingly endless ways developers check for the absence of a value:
console.log(value == null)
console.log(value === null)
console.log(value == undefined)
console.log(value === undefined)
console.log(value === undefined || value === null)
console.log(typeof value === 'undefined')
console.log(typeof value == 'undefined')
console.log(typeof value === 'undefined' || value === null)
console.log(typeof value === 'undefined' || value == null)
console.log(typeof value == 'undefined' || value == null)
console.log(typeof value == 'undefined' || value === null)
console.log(!value)
Which one is right?
The Absence of a Value ¶
In order to understand which of these expressions is correct we must first take a look at the two ways JavaScript represents the lack of a value.
Undefined ¶
undefined
is one of JavaScript’s primitive types which means checking its type using the typeof
operator returns the string 'undefined'
:
console.log(typeof undefined) // undefined
It is the default value of any declared, but unassigned variable:
var x
console.log(x) // undefined
It is the value returned when trying to access an undeclared object property:
var obj = {}
console.log(obj.a) // undefined
It is the default return value of a function which does not return:
function f() {}
console.log(f()) // undefined
It is returned by the void
operator, an operator which evaluates an expression and then returns undefined
:
console.log(void 0) // undefined
console.log(void 'hello') // undefined
console.log(void(3 + 2)) // undefined
console.log(void(/* any expression */)) // undefined
And lastly, it is not a literal. It is a property of the global object, an object that always exists in the global scope (accessible through the window
property on browsers).
Null ¶
null
is also a JavaScript primitive type, but checking its type using the typeof
operator does not return what you’d expect:
console.log(typeof null) // object
According to W3Schools you can consider this behavior a bug in JavaScript. typeof null
should return 'null'
, but since a lot of code has already been written with the assumption that typeof null
erroneously returns 'object'
, it will not be changed to avoid breaking old code.
Unlike undefined
, null
does not show up as a default value anywhere. Instead it is usually returned by functions which are expected to return an object when one could not be retrieved from the given parameters.
For example, in browsers document.getElementById
returns null
if no element with the given ID was found in the HTML document:
console.log(document.getElementById('some-id-which-no-element-has')) // null
In contrast to undefined
, null
is a literal. It is not the identifier of some property. It represents a lack of identification.
Based on these characteristics it is safe to say that both undefined
and null
represent the absence of a value. Therefore, any code we write which aims to check for the absence of a value should account for both undefined
and null
.
Equality ¶
Now that we understand undefined
and null
, we still need to briefly address the difference between ==
and ===
in order to understand the expressions at the beginning of this post.
Strict ¶
Strict equality is invoked using ===
and is relatively straight forward. If two values, a
and b
, are of different types then a === b
will return false
. However, if they are of the same type then true
is returned if their contents match and false
otherwise:
Examples:
console.log(0 === 0) // true
console.log('hello!' === 'hello!') // true
console.log(null === null) // true
console.log(undefined === undefined) // true
console.log(0 === 5) // false
console.log(0 === '0') // false
console.log(0 === 'hello!') // false
console.log(null === undefined) // false
var obj = {}
console.log(obj === {}) // false (because objects are compared by reference)
console.log(obj === obj) // true (because reference to same object)
Loose ¶
Loose quality is invoked using ==
and often produces unexpected results. If two values, a
and b
, are of the same type then a === b
is returned. However, if they are of different types then JavaScript will attempt to coerce (i.e. convert) the two values to the same type and then strictly equate the two. This second case has prompted the use of loose equality to be largely discouraged by the JavaScript community:
Examples:
console.log(1 == 1) // true
console.log(1 == '1') // true (because the string was converted to a number)
console.log('1' == 1) // true (because the string was converted to a number)
console.log(0 == false) // true (because the boolean was converted to a number)
console.log(0 == null) // false (because absence of a value is never considered equal to a concrete value)
console.log({} == {}) // false (because objects are compared by reference)
console.log(0 == undefined) // false (because absence of a value is never considered equal to a concrete value)
console.log(null == undefined) // true (because both represent the absence of a value)
console.log(undefined == null) // true (because both represent the absence of a value)
console.log("hello!" == false) // false
console.log("" == false) // true (because the string was converted to a boolean and an empty string kind of represents falsity in the realm of strings I guess?)
console.log([] == false) // true (because the array was converted to a boolean and an empty array kind of represents falsity in the realm of array I guess?)
If you’re feeling confused you wouldn’t be the only one. Check out this operand conversion table and this article about truthy and falsey values if you want to fully understand loose equality. Additionally, if you want a handy reference of how ==
and ===
behave then I would recommend this link.
Bringing It All Together ¶
It’s time to check which of the expressions from the beginning of the post work! Let’s take a look at the first expression and write a checklist to evaluate it:
console.log(value == null)
- Does it return
true
onundefined
? Yes, because substitutingundefined
forvalue
yieldsundefined == null
and as we have learned from the loose equality section,undefined
andnull
are loosely equal because both represent the absence of a value. - Does it return
true
onnull
? Yes, because substitutingnull
forvalue
yieldsnull == null
which obviously returnstrue
. - Does it return
false
on everything else? Yes, because as we have learned from the loose equality section,null
is not loosely equal to anything other than itself andundefined
because the absence of a value is never considered equal to a concrete value.
You may have noticed that value == undefined
would also work for almost the same reasons. However, value == null
is safer because the value of undefined
is not guaranteed to stay constant. Prior to JavaScript version ES5 undefined
could be reassigned since it’s simply a global property and even in the most recent versions of JavaScript undefined
can be shadowed by a local variable. This could never happen with null
because it is a literal and that makes it the objectively better choice.
It is worth noting that these issues with undefined
can be avoided by using the void
operator instead because it is guaranteed to return the expected value of undefined
. Most commonly, the expression passed to the void
operator for this purpose is 0
because void 0
is short and quick to evaluate. However, I would still not recommend using value == void 0
in place of value == null
because it may confuse other programmers (many of which are unfamiliar with the void
operator), null
is two characters shorter than void 0
, and void 0
may be marginally slower than null
since 0
must be evaluated before undefined
is returned.
These methods work except for one lurking issue. All of our questions assume that we know for a fact that value
has been declared and we have access to it. However, if value
is undeclared our code will throw a ReferenceError
. This may seem absurd because don’t we always know if a variable has been declared or not? Unfortunately this is not always the case.
Many JavaScript libraries aim to be platform agnostic. They are designed in such a way which allows them to run in the browser, on the server, or as a Node.js module. In Node.js there is a global variable module
which can be used to export methods for use in other modules, but on the browser this variable is never declared. Therefore, if we execute module == null
on Node.js it would return false
, but on browsers it would throw a ReferenceError
! One way to handle this issue would be to use try
catch
blocks to catch the ReferenceError
and resume execution in the case we’re not running on Node.js:
try {
value // expression statement will throw a ReferenceError if value is an undeclared variable
console.log('value is declared') // will log if the previous statement did not throw an error
} catch (err) {
console.log('value is undeclared') // will log if a ReferenceError was thrown
}
Note that if any code following the first statement in the try
block throws an error for some other reason then the catch
block would be executed even though value
was declared. This issue can be avoided by checking that the thrown error was specifically a ReferenceError
using the instanceof
operator:
try {
value
console.log('value is declared')
/* some potentially error-throwing code */
} catch (err) {
if (err instanceof ReferenceError) {
console.log('value is undeclared')
} else {
console.log('Some other error occurred')
}
}
Note that this solution only works if the potentially error-throwing code does not also throw a ReferenceError
because it would also match the if condition. I cannot think of any reason anyone would do this on purpose. This situation would likely arise due to misspelling the name of a declared variable. For this reason you should try to keep the code in the try
catch
blocks as short as possible. The if condition could also be altered to check the ReferenceError
message string
for our specific variable err instanceof ReferenceError && err.message.split(' ')[0] === 'value'
, but I do not recommend it because it assumes your code has misspelled variables names which can and should be debugged and fixed.
The code with the if condition kept the same is a good solution if you specifically want to check if a variable is declared or not. However, if you want to classify undeclared variables as absent values and lump them in with undefined
and null
then fortunately there is a better solution. It turns out that checking the type of an undeclared variable using the typeof
operator will not throw a ReferenceError
, but will return the string 'undefined'
instead. This is convenient because checking the type of a declared variable with a value of undefined
using the typeof
operator will also return the string 'undefined'
. So the expression typeof value === 'undefined'
also checks off the first item on our checklist! However, it doesn’t take into account if value
is null
so we must add an additional check in an or clause:
console.log(typeof value === 'undefined' || value === null)
- Does it return
true
on whenvalue
is undeclared? Yes, because checking the type of an undeclared variable using thetypeof
operator returns the string'undefined'
which after substituting gives us'undefined' === 'undefined'
in the first condition which obviously returnstrue
, and because the first condition istrue
the expression short-circuits and allows us to avoid theReferenceError
which would have been caused byvalue === null
. The prevention of the error-throwing code’s execution by short-circuiting shows why the order of the two conditions cannot be switched. - Does it return
true
onundefined
? Yes, because substitutingundefined
forvalue
yieldstypeof undefined === 'undefined'
in the first condition, which simplifies to'undefined' === 'undefined'
and obviously returnstrue
. - Does it return
true
onnull
? Yes, because although substitutingnull
forvalue
in the first condition fails due totypeof null === 'undefined'
simplifying to'object' === 'undefined'
, substitutingnull
forvalue
in the second condition yieldsnull === null
which obviously returnstrue
. - Does it return
false
on everything else? Yes, because checking the type of any concrete value using thetypeof
operator will not return'undefined'
so the first condition returnsfalse
, and substituting any concrete value in the second condition will also returnfalse
becausenull
is only strictly equal to itself.
This method works in every situation, but it is slower than value == null
. The optimal strategy is to use this method when you don’t know if value
has been declared and use the previous method when you do. This is the approach taken by CoffeeScript when transpiling its existential operator to JavaScript.
You may have noticed that a few of the expressions at the beginning of the post look almost identical to the expression we just evaluated. Interestingly enough the following four expressions share the same behavior:
console.log(typeof value === 'undefined' || value === null)
console.log(typeof value === 'undefined' || value == null)
console.log(typeof value == 'undefined' || value == null)
console.log(typeof value == 'undefined' || value === null)
So why did we choose the expression with strict equality in both conditions?
- Strict equality is no slower than loose equality because they both check the operand types.
- Strict equality is faster than loose equality when the types of the operands differ because it can immediately return
false
without having to coerce the operand types. - Loose equality often produces unexpected results and should be avoided if possible.
The second bullet point makes a strong argument for using strict equality for the second condition because value
may not be the same type as null
, but in the first condition both typeof value
and 'undefined'
are guaranteed to be of type string
so the decision to use strict equality is only supported by the first and third bullet points. This makes the first expression above the best choice.
Lastly, let’s evaluate the rest of the expressions from the beginning of the post:
console.log(value === null) // doesn't account for undefined
console.log(value === undefined) // doesn't account for null
console.log(value === undefined || value === null) // works, but is simply a slower version of value == null and value == undefined
console.log(typeof value === 'undefined') // doesn't account for null
console.log(typeof value == 'undefined') // doesn't account for null
console.log(!value) // erroneously returns true for falsey values such as false, '', [], 0, etc.
Object Properties ¶
When checking for the absence of a value in an object property, additional considerations must be made regarding the property itself. Consider the following example where we use value == null
to check for the absence of a value in each object’s key
property:
var obj1 = {}
var obj2 = {
key: undefined
}
console.log(obj1.key == null) // true
console.log(obj2.key == null) // true
An object without a key
property produces the same result as an object with its key
property set to a value of undefined
. This is because in contrast to undeclared variables, trying to access the value of an undeclared property always returns undefined
. This means that value == null
is a good solution if you want to classify undeclared properties as absent values and lump them in with undefined
and null
. However, if you specifically want to check if a property is declared or not then a different method must be used.
One way is to use the in
operator:
var obj1 = {}
var obj2 = {
key: undefined
}
console.log('key' in obj1) // false
console.log('key' in obj2) // true
Note that a string
or Symbol
containing the property name must be used on the lefthand side of the in
operator, not a token. This may seem like a good solution, but consider the following case:
var obj1 = {}
var obj2 = {
constructor: undefined
}
console.log('constructor' in obj1) // true
console.log('constructor' in obj2) // true
Probably not what you expected right? The expression 'constructor' in obj1
returns true
because the constructor
property was inherited from the object’s prototype chain. This means that the in
operator considers both the specific properties of the object as well as inherited properties.
Fortunately, there is a way to check just the specific uninherited properties of the object using the hasOwnProperty
method, which itself is inherited from the Object
constructor, or class in object oriented terms:
var obj1 = {}
var obj2 = {
constructor: undefined
}
console.log(obj1.hasOwnProperty('constructor')) // false
console.log(obj2.hasOwnProperty('constructor')) // true
Note that unlike the in
operator, the hasOwnProperty
method can only take a string
argument. There is one caveat to using the hasOwnProperty
method. Consider the following case:
var obj = {
hasOwnProperty: function () {
return true
}
}
console.log(obj.hasOwnProperty('wow')) // true
The hasOwnProperty
method of the Object
constructor was shadowed, or overridden in object oriented terms, by a method which always returns true
. Accessing properties always prefers uninherited to inherited ones which is why true
was returned for the name of an undeclared property. Fortunately there is a way around this. The hasOwnProperty
method can be accessed directly from the Object
constructor and called with this
as a specified value using the call
method of the Function
constructor. The call
method takes the value of this
as its first argument and the arguments to the called function as the rest of its arguments:
var obj = {
hasOwnProperty: function () {
return true
}
}
console.log(Object.prototype.hasOwnProperty.call(obj, 'wow')) // false
If you find yourself using this method more than once I would recommend extracting it out as a function:
function hasOwnProperty(obj, property) {
return Object.prototype.hasOwnProperty.call(obj, property)
}
var obj = {
hasOwnProperty: function () {
return true
}
}
console.log(hasOwnProperty(obj, 'wow')) // false
Conclusion ¶
To recap here are the optimal expressions.
Checking if a variable is declared:
try {
value
// value is declared
} catch (err) {
if (err instanceof ReferenceError) {
// value is undeclared
} else {
// some other error occurred
}
}
Checking for the absence of an uninherited property in an object when the object definitely doesn’t have a shadowing hasOwnProperty
property:
!obj.hasOwnProperty(key)
Checking for the existence of an uninherited property in an object when the object definitely doesn’t have a shadowing hasOwnProperty
property:
obj.hasOwnProperty(key)
Checking for the absence of an uninherited property in an object when the object may have a shadowing hasOwnProperty
property:
!Object.prototype.hasOwnProperty.call(obj, key)
Checking for the existence of an uninherited property in an object when the object may have a shadowing hasOwnProperty
property:
Object.prototype.hasOwnProperty.call(obj, key)
Checking for the absence of an inherited or uninherited property in an object:
!(key in obj)
Checking for the existence of an inherited or uninherited property in an object:
key in obj
Checking for the absence of a value when the value may be an undeclared variable:
typeof value === 'undefined' || value === null
Checking for the existence of a value when the value may be an undeclared variable (derived using De Morgan’s Law):
typeof value !== 'undefined' && value !== null
Checking for the absence of a value when the value is definitely declared:
value == null
Checking for the existence of a value when the value is definitely declared:
value != null
Checking for the absence of a value when the value is definitely declared and you want to avoid loose equality:
value === null || value === void 0
Checking for the existence of a value when the value is definitely declared and you want to avoid loose equality (derived using De Morgan’s Law):
value !== null && value !== void 0
Feel free to use combinations of these to fit your needs. For example, here’s how you would check if an object has an uninherited property which has an absent value such as undefined
or null
when the object definitely doesn’t have a shadowing hasOwnProperty
property:
obj.hasOwnProperty(key) && obj[key] == null
Thank you for reading!