Chapter 8. Avoid Null Comparisons

A common yet still problematic pattern in JavaScript is testing a variable against null, presumably to determine whether the variable has been filled in with an appropriate value. For example:

var Controller = {
    process: function(items) {
        if (items !== null) {     // Bad
            items.sort();
            items.forEach(function(item) {
                // do something
            });
        }
    }
};

Here, the process() method is clearly expecting that items will be an array, as indicated by the use of sort() and forEach(). The intention of this code is clear: don’t continue processing unless the items argument contains an array. The problem with this approach is that the comparison against null doesn’t actually prevent future errors. The value of items could be 1, or a string, or some random object. All of these are technically not equal to null and would therefore cause the process() method to fail once sort() executes.

Comparing a variable against only null typically doesn’t give you enough information about the value to determine whether it’s safe to proceed. Fortunately, JavaScript gives you a number of ways to determine the true value of a variable.

Detecting Primitive Values

There are five primitive types in JavaScript: string, number, boolean, null, and undefined. If you are expecting a value to be a string, number, boolean, or undefined, then the typeof operator is your best option. The typeof operator works on a variable and returns a string indicating the type of value:

  • For strings, typeof returns “string”.

  • For numbers, typeof returns “number”.

  • For booleans, typeof returns “boolean”.

  • For undefined, typeof returns “undefined”.

Basic syntax for typeof is as follows:

typeof variable

You may also see typeof used in this manner:

typeof(variable)

Although this is valid JavaScript syntax, this pattern makes typeof appear to be a function instead of an operator. For this reason, the pattern without parentheses is recommended.

Using typeof for detecting these four primitive value types is the safest way to code defensively. Here are some examples:

// detect a string
if (typeof name === "string") {
    anotherName = name.substring(3);
}

// detect a number
if (typeof count === "number") {
    updateCount(count);
}

// detect a boolean
if (typeof found === "boolean" && found) {
    message("Found!");
}

// detect undefined
if (typeof MyApp === "undefined") {
    MyApp = {    
        // code        
    };
}

The typeof operator is also unique in that it can be used on an undeclared variable without throwing an error. Both undeclared variables and variables whose value is undefined return “undefined” when typeof is used.

The last primitive type, null, is the one that you normally shouldn’t be testing for. As stated earlier, comparing simply against null generally doesn’t give you enough information about whether the value is expected. There is one exception: if one of the expected values is actually null, then it is okay to test for null directly. The comparison should be done using either === or !== against null. For example:

// If you must test for null, this is the way to do it
var element = document.getElementById("my-div");
if (element !== null) { 
    element.className = "found";
}

It is entirely possible for document.getElementById() to return null if the given DOM element isn’t found. The method will return either null or an element. Because null is one of the expected outcomes, it’s okay to test for it using !==.

Note

Running typeof null returns “object”, making this an inefficient way to test for null values. If you must test for null, use the identically equal operator (===) or the not identically equal (!==) operator.

Detecting Reference Values

Reference values are also known as objects. In JavaScript, any value that isn’t a primitive is definitely a reference. There are several built-in reference types such as Object, Array, Date, and Error, just to name a few. The typeof operator is of little use for reference values, because it returns “object” for any type of object:

console.log(typeof {});             // "object"
console.log(typeof []);             // "object"
console.log(typeof new Date());     // "object"
console.log(typeof new RegExp());   // "object"

Another downside to using typeof for objects is that typeof returns “object” for null values as well:

console.log(typeof null);           // "object"

This quirk, which has been recognized as a serious bug in the specification, prevents accurate detection of null using typeof.

The instanceof operator is the best way to detect values of a particular reference type. Basic syntax for instanceof is:

value instanceof constructor

Here are some examples:

// detect a Date
if (value instanceof Date) {
    console.log(value.getFullYear());
}

// detect a RegExp
if (value instanceof RegExp) {
    if (value.test(anotherValue)) {
        console.log("Matches");
    }
}

// detect an Error
if (value instanceof Error) {
    throw value;
}

An interesting feature of instanceof is that it not only checks the constructor used to create the object but also checks the prototype chain. The prototype chain contains information about the inheritance pattern used to define the object. For instance, every object inherits from Object by default, so every object returns true for value instanceof Object. For example:

var now = new Date();

console.log(now instanceof Object);     // true
console.log(now instanceof Date);       // true

Due to this behavior, it’s typically not good enough to use value instanceof Object when you’re expecting a particular type of object.

The instanceof operator also works with custom types that you’ve defined for yourself. For instance:

function Person(name) {
    this.name = name;
}

var me = new Person("Nicholas");

console.log(me instanceof Object);      // true
console.log(me instanceof Person);      // true

This example creates a custom Person type. The me variable is an instance of Person, so me instanceof Person is true. As mentioned previously, all objects are also considered instances of Object, so me instanceof Object is also true.

The instanceof operator is the only good way to detect custom types in JavaScript. It’s also good to use for almost all built-in JavaScript types. There is, however, one serious limitation.

Suppose that an object from one browser frame (frame A) is passed into another (frame B). Both frames have the constructor function Person defined. If the object from frame A is an instance of Person in frame A, then the following rules apply:

// true
frameAPersonInstance instanceof frameAPerson

// false
frameAPersonInstance instanceof frameBPerson

Because each frame has its own copy of Person, it is considered an instance of only that frame’s copy of Person, even though the two definitions may be identical.

This issue is a problem not just for custom types but also for two very important built-in types: functions and arrays. For these two types, you don’t want to use instanceof at all.

Detecting Functions

Functions are technically reference types in JavaScript and also technically have a Function constructor of which each function is an instance. For example:

function myFunc() {}

// Bad
console.log(myFunc instanceof Function);        // true

However, this approach doesn’t work across frames due to each frame having its own Function constructor. Fortunately, the typeof operator also works with functions, returning “function”:

function myFunc() {}

// Good
console.log(typeof myFunc === "function");       // true

Using typeof is the best way to detect functions, because it also works across frames.

There is one limitation to typeof’s function detection. In Internet Explorer 8 and earlier, any functions that are part of the DOM (such as document.getElementById()) return “object” instead of “function” when used with typeof. For instance:

// Internet Explorer 8 and earlier
console.log(typeof document.getElementById);        // "object"
console.log(typeof document.createElement);         // "object"
console.log(typeof document.getElementsByTagName);  // "object"

This quirk arises due to how the browser implements the DOM. In short, these early versions of Internet Explorer didn’t implement the DOM as native JavaScript functions, which caused the native typeof operator to identify the functions as objects. Because the DOM is so well defined, developers typically test for DOM functionality using the in operator, understanding that the presence of the object member means that it’s a function, as in:

// detect DOM method
if ("querySelectorAll" in document) {
    images = document.querySelectorAll("img");
}

This code checks to see whether querySelectorAll is defined in document, and if so, goes on to use that function. Though not ideal, this is the safest way to check for the presence of DOM methods if you need to support Internet Explorer 8 and earlier. In all other cases, the typeof operator is the best way to detect functions in JavaScript.

Detecting Arrays

Passing arrays back and forth between frames was one of the original cross-frame issues in JavaScript. Developers quickly discovered that instanceof Array didn’t always produce appropriate results in these cases. As mentioned previously, each frame has its own Array constructor, so an instance from one frame isn’t recognized in another. Douglas Crockford first recommended performing some duck typing, testing for the presence of the sort() method:

// Duck typing arrays
function isArray(value) {
    return typeof value.sort === "function";
}

This detection relies on the fact that arrays are the only object types with a sort() method. Of course, this version of isArray() will also return true when any object with a sort() method is passed in.

There was quite a lot of investigation into accurately detecting array types in JavaScript; ultimately, Juriy Zaytsev (also known as Kangax) proposed an elegant solution to this problem:

function isArray(value) {
    return Object.prototype.toString.call(value) === "[object Array]";
}

Kangax found that calling the native toString() method on a given value produced a standard string in all browsers. For arrays, the string is “[object Array]”, and this call worked regardless of the frame from which the array originated. Kangax’s approach quickly became popular and is now implemented in most JavaScript libraries.

Note

This approach is generally useful for identifying native objects as opposed to developer-defined objects. For example, the native JSON object returns “[object JSON]” using this technique.

Since that time, ECMAScript 5 has introduced Array.isArray() formally into JavaScript. The sole purpose of this method is to accurately determine whether a value is an array. As with Kangax’s function, Array.isArray() works with values that are passed across frames, so many JavaScript libraries now implement methods similar to this:

function isArray(value) {
    if (typeof Array.isArray === "function") {
        return Array.isArray(value);
    } else {
        return Object.prototype.toString.call(value) === "[object Array]";
    }
}

The Array.isArray() method is implemented in Internet Explorer 9+, Firefox 4+, Safari 5+, Opera 10.5+, and Chrome.

Detecting Properties

Another time when when developers typically use null (and also undefined) is when trying to determine whether a property is present in an object. For example:

// Bad: Checking falsyness
if (object[propertyName]) {
    // do something
}

// Bad: Compare against null
if (object[propertyName] != null) {
    // do something
}

// Bad: Compare against undefined
if (object[propertyName] != undefined) {
    // do something
}

Each of these examples is actually checking the value of the property with the given name rather than the existence of the property with the given name, which can result in errors when you’re dealing with falsy values such as 0, "" (empty string), false, null, and undefined. After all, these are all valid values for properties. For example, if the property is keeping track of a number, the value might very well be zero. In that case, the first example will likely cause a bug. Likewise, if the property value could be null or undefined, all three examples can cause bugs.

The best way to detect the presence of a property is to use the in operator. The in operator simply checks for the presence of the named property without reading its value, avoiding ambiguity with statements such as those earlier in this section. If the property either exists on the instance or is inherited from the object’s prototype, the in operator returns true. For example:

var object = {
    count: 0,
    related: null
};

// Good
if ("count" in object) {
    // this executes
}

// Bad: Checking falsy values
if (object["count"]) {
    // this doesn't execute
}

// Good
if ("related" in object) {
    // this executes
}

// Bad: Checking against null
if (object["related"] != null) {
    // doesn't execute
}

If you only want to check for the existence of the property on the object instance, then use the hasOwnProperty() method. All JavaScript objects that inherit from Object have this method, which returns true when the property exists on the instance (if the property only exists on the prototype, in which case it returns false). Keep in mind that DOM objects in Internet Explorer 8 and earlier do not inherit from Object and therefore do not have this property. That means you’ll need to check for the existence of hasOwnProperty() before using it on potential DOM objects (if you know the object isn’t from the DOM, you can omit this step).

// Good for all non-DOM objects
if (object.hasOwnProperty("related")) {
    //this executes
}

// Good when you're not sure
if ("hasOwnProperty" in object && object.hasOwnProperty("related")) {
    //this executes
}

Because of Internet Explorer 8 and earlier, I tend to use the in operator whenever possible and only use hasOwnProperty() when I need to be sure of an instance property.

Whenever you want to check for the existence of the property, make sure to use the in operator or hasOwnProperty(). Doing so can avoid a lot of bugs.

Note

Of course, if you want to specifically check for the values of null or undefined, use the guidelines in Chapter 1.