Checking for the Absence of a Value in JavaScript
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)
//=> undefinedThe value of a declared unassigned variable is undefined:
let x
console.log(x)
//=> undefinedThe access of an absent object property returns undefined:
const object = {}
console.log(object.absentProperty)
//=> undefinedThe return value of a function that doesn’t explicitly return is undefined:
function f() {}
console.log(f())
//=> undefinedThe void operator always returns undefined1:
console.log(void 0)
//=> undefined
console.log(void 'hello')
//=> undefined
console.log(void (3 + 2))
//=> undefined
console.log(void (/* any expression */))
//=> undefinedLastly, 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)
//=> objectIdeally 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'))
//=> nullUnlike 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
trueforundefined? Yes, becauseundefinedandnullare loosely equal.Does it evaluate to
truefornull? Yes, becausenullis equal to itself.Does it evaluate to
falsefor everything else? Yes, becausenullis 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
truewhenvalueis undeclared? Yes, because thetypeofoperator returns'undefined'for an undeclared variable.Does it evaluate to
trueforundefined? Yes, becausetypeof undefinedreturns'undefined'.Does it evaluate to
truefornull? Yes, the first condition evaluates tofalse, but the second condition evaluates totruebecausenullis equal to itself.Does it evaluate to
falsefor everything else? Yes, thetypeofoperator only returns'undefined'for undeclared variables andundefined, andnullis 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)
//=> trueThe 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)
//=> trueNote 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)
//=> trueProbably 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)
//=> falseBut 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'))
//=> trueThere 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 functionobject1’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’shasOwnPropertymethod directly:const object = { hasOwnProperty: () => true } console.log(Object.prototype.hasOwnProperty.call(object, 'property')) //=> falseUse
Object’s statichasOwnmethod:const object = { hasOwnProperty: () => true } console.log(Object.hasOwn(object, 'property')) //=> falsehasOwnwas added to JavaScript to avoidhasOwnProperty’s pitfalls, but at the time of writing it is relatively new.
Recap
Checking if value is set to an absent value:
value == nullChecking if value is undeclared or set to an absent value:
typeof value === 'undefined' || value === nullChecking if 'property' in object is absent or set to an absent value:
object.property == nullChecking 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
voidoperator is useful. ↩CoffeeScript follows the same principle when transpiling its existential operator to JavaScript. ↩