You can also read this post on my website!
JavaScript has a lot of similar-looking ways to 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?
Absent values
JavaScript has two representations of an absent value.
Undefined
undefined
is a JavaScript primitive type. The typeof
operator returns 'undefined'
for undefined
:
console.log(typeof undefined)
//=> undefined
The value of a declared unassigned variable is undefined
:
let x
console.log(x)
//=> undefined
The access of an absent object property returns undefined
:
const object = {}
console.log(object.absentProperty)
//=> undefined
The return value of a function that doesn’t explicitly return is undefined
:
function f() {}
console.log(f())
//=> undefined
The void
operator always returns undefined
1:
console.log(void 0)
//=> undefined
console.log(void 'hello')
//=> undefined
console.log(void (3 + 2))
//=> undefined
console.log(void (/* any expression */))
//=> undefined
Lastly, undefined
is not a literal! It is a property of the global object, which always exists in the global scope.
Null
null
is also a JavaScript primitive type, but typeof
returns something unexpected for null
:
console.log(typeof null)
//=> object
Ideally typeof null
would return 'null'
, but typeof null
has returned 'object'
since JavaScript’s inception and it would break existing code if the behavior were changed now.
null
does not appear as a “default” value in JavaScript in the same way that undefined
does. Instead, developers typically make functions return null
when an object can’t be found or constructed. For example, in browsers document.getElementById
returns null
if there’s no element in the document with the given ID:
console.log(document.getElementById('some-id-that-no-element-has'))
//=> null
Unlike undefined
, null
is a literal. It is not a property of the global object.
Equality
Now that we’ve covered undefined
and null
, let’s address the difference between ==
and ===
.
Strict
Strict equality is invoked using ===
. For two values, a
and b
, a === b
evaluates to true
if a
and b
have the same type and their values are equal. Otherwise, a === b
evaluates to false
:
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 (same types, but different values)
console.log(0 === '0')
//=> false (different types)
console.log(0 === 'hello!')
//=> false (different types)
console.log(null === undefined)
//=> false (different types)
const object = {}
console.log(object === {})
//=> false (because objects are compared by reference)
console.log(object === object)
//=> true (because the object is referentially equal to itself)
Loose
Loose equality is invoked using ==
and often produces unexpected results. For two values of the same type, a
and b
, a == b
behaves like a === b
. If a
and b
have different types, then JavaScript coerces the values to the same type and strictly equates them:
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 absent values are not considered equal to non-absent values)
console.log({} == {})
//=> false (because objects are compared by reference)
console.log(0 == undefined)
//=> false (because absent values are not considered equal to non-absent values)
console.log(null == undefined)
//=> true (because both are absent values)
console.log(undefined == null)
//=> true (because both are absent values)
console.log('hello!' == false)
//=> false
console.log('' == false)
//=> true (because the string was converted to a boolean and an empty string sort of represents false in the realm of strings I guess)
console.log([] == false)
// true (because the array was converted to a boolean and an empty array sort of represents false in the realm of arrays I guess)
If you’re feeling confused, then you wouldn’t be the only one. This operand conversion table and article about “truthy” and “falsy” values explain loose equality more fully. For a handy reference on the behavior of ==
and ===
, look no further than this JavaScript equality table.
The right way to check for an absent value
Now we can check which expressions from the beginning of the post work! Let’s take a look at the first expression:
console.log(value == null)
Does it evaluate to
true
forundefined
? Yes, becauseundefined
andnull
are loosely equal.Does it evaluate to
true
fornull
? Yes, becausenull
is equal to itself.Does it evaluate to
false
for everything else? Yes, becausenull
is only loosely equal to itself andundefined
.
value == undefined
would also work for roughly the same reasons, but value == null
is safer because undefined
could be shadowed or reassigned in pre-ES5 JavaScript environments. This can’t happen with null
because it is a literal.
Undeclared variables
These methods work except for one lurking issue. If value
is undeclared, then our code would throw a ReferenceError
.
That may sound like a nonissue, but consider that some JavaScript needs to be compatible with both the browser and Node.js, and that the two environments differ in which global variables are declared. For example, in the browser the global variable window
is declared, but there’s no such variable in Node.js. Can we access the window
variable only if it exists and avoid a ReferenceError
?
It turns out that the typeof
operator returns 'undefined'
for an undeclared variable instead of throwing a ReferenceError
. This is convenient because typeof undefined
also returns 'undefined'
so typeof value === 'undefined'
checks for both undeclared variables and undefined
. To check for null
as well we can add an additional check using logical “or”.
console.log(typeof value === 'undefined' || value === null)
Does it evaluate to
true
whenvalue
is undeclared? Yes, because thetypeof
operator returns'undefined'
for an undeclared variable.Does it evaluate to
true
forundefined
? Yes, becausetypeof undefined
returns'undefined'
.Does it evaluate to
true
fornull
? Yes, the first condition evaluates tofalse
, but the second condition evaluates totrue
becausenull
is equal to itself.Does it evaluate to
false
for everything else? Yes, thetypeof
operator only returns'undefined'
for undeclared variables andundefined
, andnull
is only strictly equal to itself.
This method works in every situation, but it is only preferable over value == null
when you don’t know if value
has been declared2.
The problems with the other expressions
A few of the expressions at the beginning of the post look almost identical to the expression we just evaluated. In fact, the following expressions are equivalent to 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(typeof value == 'undefined' || value === null)
So why choose the expression that uses strict equality for both conditions? I prefer to avoid loose equality because it’s confusing and in this case it’s not required for correct behavior.
Let’s evaluate the rest of the expressions from the beginning of the post:
// Doesn't account for undefined
console.log(value === null)
// Doesn't account for null
console.log(value === undefined)
// Works, but is much more verbose than value == null
console.log(value === undefined || value === null)
// Doesn't account for null
console.log(typeof value === 'undefined')
// Doesn't account for null
console.log(typeof value == 'undefined')
// Erroneously evaluates to true for falsy values such as false, '', [], and 0
console.log(!value)
Absent object properties
An object property can be set to an absent value, but the property itself can also be absent:
const object1 = {}
const object2 = { property: undefined }
console.log(object1.property == null)
//=> true
console.log(object2.property == null)
//=> true
The result for the two objects is the same because the access of an absent property returns undefined
. This makes value == null
a good solution when checking for null
, undefined
, and absent properties. However, specifically checking for an absent property requires a different method.
One way is to use the in
operator:
const object1 = {}
const object2 = { property: undefined }
console.log('property' in object1)
//=> false
console.log('property' in object2)
//=> true
Note that the left-hand side of the in
operator must be a string
or Symbol
, not an identifier. This may seem like a good solution, but consider the following case:
const object1 = {}
const object2 = { constructor: undefined }
console.log('constructor' in object1)
//=> true
console.log('constructor' in object2)
//=> true
Probably not what you expected right? The expression 'constructor' in object1
returns true
because the constructor
property was inherited from the object’s prototype chain. The in
operator considers both the specific properties of the object as well as its inherited properties.
This a nonissue when the object has a null
prototype because there are no inherited properties:
const object = Object.create(null)
console.log(`constructor` in object)
//=> false
But most of the time the object doesn’t have a null
prototype or we don’t know if it does. A more robust solution is to only check the uninherited properties using the hasOwnProperty
method, which is inherited from Object
:
const object1 = {}
const object2 = { constructor: undefined }
console.log(object1.hasOwnProperty('constructor'))
//=> false
console.log(object2.hasOwnProperty('constructor'))
//=> true
There are a couple of pitfalls to using the hasOwnProperty
method:
const object1 = { hasOwnProperty: () => true }
const object2 = Object.create(null)
console.log(object1.hasOwnProperty('property'))
//=> true
console.log(object2.hasOwnProperty('property'))
//=> TypeError: object2.hasOwnProperty is not a function
object1
’s hasOwnProperty
method was shadowed by a method that always returns true
. object2
was created with a null
prototype so it does not inherit hasOwnProperty
from Object
. There are two ways around these pitfalls:
Access
Object
’shasOwnProperty
method directly:Use
Object
’s statichasOwn
method:
Recap
Checking if value
is set to an absent value:
value == null
Checking if value
is undeclared or set to an absent value:
typeof value === 'undefined' || value === null
Checking if 'property'
in object
is absent or set to an absent value:
object.property == null
Checking if property
in object
is absent:
!Object.prototype.hasOwnProperty.call(object, 'property')
Checking if property
in object
is absent in modern browsers:
!Object.hasOwn(object, 'property')
Footnotes
MDN has some examples of when the
void
operator is useful. ↩CoffeeScript follows the same principle when transpiling its existential operator to JavaScript. ↩
Top comments (0)