typeof, == and ===

Fri, 16 July 2010

In JavaScript nothing is what it looks like; you rarely work with actual values.

Most of the time values morph from one type to another, just so they can morph to yet another type later on. That's the way of language. And if that weren't enough, I recently found that the way I was writing some functions in JavaScript was wrong. It wasn’t particularly my fault, though, as it looks like most of us did it wrong the way.

The root of evil is our use of the typeof operator.

Lets look at this example:

var attrWhiteList = /^(text|size|color|weight|true)$/;
function setAttr(name, value) {
    if (typeof name == "string" && attrWhiteList.test(name)) {
        this[name] = value;
    } else if (typeof name == "object" && name) {
        for (var key in name) if (attrWhiteList.test(key)) {
            this[key] = name[key];
        }
    }
}

We can call the setAttr function like this:

setAttr("size", 20);

or like this:

setAttr({size: 20, name: "Jason"});

And when we call it successfully an attribute is set. And it’s not only setting what we send, it additionally checks that attributes appear in a white-list before setting them. All seems good. Or is it?

Let’s rewrite the function with our revised view in mind:

var attrWhiteList = /^(text|size|color|weight|true)$/;
function setAttr(name, value) {
    if (arguments.length > 1) {
        if (attrWhiteList.test(name)) {
            this[name] = value;
        }
    } else if (name === Object(name)) {
        for (var key in name) if (attrWhiteList.test(key)) {
            this[key] = name[key];
        }
    }
}

Simply, our second approach robustly deals with any parameters that the function receives, while the first will not:

setAttr("size", 20);
setAttr({size: 20, name: "Jason"});

Those two work as before, but there are many parameters that the first version can’t cope with:

setAttr(true, true);

var f = function () {};
f.size = 5;
setAttr(f);

setAttr(new String("text"), "Jason");
setAttr(["text"], "Jason");
setAttr(new Boolean(true), true);
setAttr({toString: function () {return "text";}}, "Jason");

You’re probably saying: “Nobody will write code like that”. And you’re probably right, but people could easily write code like this:

setAttr(a, b);

What am I trying to say? In JavaScript you coerce types all the time, whether you intend to or not. You can fight it, or you can use it.

It doesn’t matter what type the variable is, it’s more important how you can use it. So, for example, instead of asking “Is it a number?” it’d be better to ask “Is it numeric?”

var isnan = /^(NaN|-?Infinity)$/;
function isNumeric(num) {
    return !isnan.test(+num);
}

or maybe “Is it object?” or “Is it array?”

function is(o, type) {
    type = String(type).toLowerCase();
    return  (type == "null" && o === null) ||
            (type == typeof o) ||
            (type == "object" && o === Object(o)) ||
            (type == "array" && Array.isArray && Array.isArray(o)) ||
            Object.prototype.toString.call(o).slice(8, -1).toLowerCase() == type;
}

is(function(){}, "object"); // true
is(function(){}, "function"); // true
is([], "object"); // true
is(new String("string"), "string"); // true

The difference can be subtle as it’s between “Is this variable’s type ‘object’?” and “Is this an object?”.

As you know a function is an object and function… new Number is an object and a number…

This way of thinking about JavaScript isn’t limited to typing—it impacts the comparison operator, also.

You’ll often hear that “==” is a bad part of JavaScript. I not so sure. It is often better to use “===” instead of “==” only if you don’t clearly understand how those operators work or are simply in doubt.

It depends on what level of equality you really need.

Typing: typeof a === "object"
looks as ridiculous to me as: ((typeof(a)) === ("object"))

The brackets don’t hurt, but putting them everywhere just because you used to is redundant. And it’s the same with “===”.

Taking a look at our “is” function, you’ll notice I don’t care if type is a string or not as long as it equals (==) “object” or “null”, etc. We’re embracing the flexibility of the language, and not fighting against it.

Don’t code as though the types are fixed, they’re not.

Next time you’re going to use typeof or “===” think about whether it’s the best choice. Remember that in JavaScript nothing is always what it looks like.