DEV Community

Tomer Aberbach
Tomer Aberbach

Posted on • Originally published at tomeraberba.ch on

Checking for the Absence of a Value in JavaScript

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

The value of a declared unassigned variable is undefined:

let x
console.log(x)
//=> undefined
Enter fullscreen mode Exit fullscreen mode

The access of an absent object property returns undefined:

const object = {}
console.log(object.absentProperty)
//=> undefined
Enter fullscreen mode Exit fullscreen mode

The return value of a function that doesn’t explicitly return is undefined:

function f() {}
console.log(f())
//=> undefined
Enter fullscreen mode Exit fullscreen mode

The 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 */))
//=> undefined
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode
  • Does it evaluate to true for undefined? Yes, because undefined and null are loosely equal.

  • Does it evaluate to true for null? Yes, because null is equal to itself.

  • Does it evaluate to false for everything else? Yes, because null is only loosely equal to itself and undefined.

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)
Enter fullscreen mode Exit fullscreen mode
  • Does it evaluate to true when value is undeclared? Yes, because the typeof operator returns 'undefined' for an undeclared variable.

  • Does it evaluate to true for undefined? Yes, because typeof undefined returns 'undefined'.

  • Does it evaluate to true for null? Yes, the first condition evaluates to false, but the second condition evaluates to true because null is equal to itself.

  • Does it evaluate to false for everything else? Yes, the typeof operator only returns 'undefined' for undeclared variables and undefined, and null 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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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’s hasOwnProperty method directly:

  • Use Object’s static hasOwn method:

Recap

Checking if value is set to an absent value:

value == null
Enter fullscreen mode Exit fullscreen mode

Checking if value is undeclared or set to an absent value:

typeof value === 'undefined' || value === null
Enter fullscreen mode Exit fullscreen mode

Checking if 'property' in object is absent or set to an absent value:

object.property == null
Enter fullscreen mode Exit fullscreen mode

Checking if property in object is absent:

!Object.prototype.hasOwnProperty.call(object, 'property')
Enter fullscreen mode Exit fullscreen mode

Checking if property in object is absent in modern browsers:

!Object.hasOwn(object, 'property')
Enter fullscreen mode Exit fullscreen mode

Footnotes

  1. MDN has some examples of when the void operator is useful.

  2. CoffeeScript follows the same principle when transpiling its existential operator to JavaScript.

Top comments (0)