2012-01-20

What is JavaScript’s typeof operator used for?

JavaScript’s typeof is a complicated beast – it can be used for many things, but also has many quirks. This post lists its use cases, points out problems and presents alternatives.

You should be familiar with the difference between primitives and objects [1].

Checking whether a variable exists and has a value

typeof returns "undefined" in two cases: Either a variable has not been declared or the given value is undefined. Examples:
    > typeof undeclaredVariable === "undefined"
    true

    > var declaredVariable;
    > typeof declaredVariable
    'undefined'

    > typeof undefined
    'undefined'
There are other ways to check whether a value is undefined:
    > var value = undefined;
    > value === undefined
    true
But they throw an exception when used with an undeclared variable – only typeof allows you to do that:
    > undeclaredVariable === undefined
    ReferenceError: undeclaredVariable is not defined
Note: uninitialized variables, missing parameters and non-existent properties are not problematic, because they can all be accessed and have the value undefined:
    > var declaredVariable;
    > declaredVariable === undefined
    true

    > (function (x) { return x === undefined }())
    true

    > ({}).foo === undefined
    true
Problem: typeof is verbose when used for this task.

Better: This is a rare use case, so there might not be a need for a better solution. One could introduce a dedicated operator:

    > defined undeclaredVariable
    false
    
    > var declaredVariable;
    > defined declaredVariable
    false
Alternatively, one might also want to just check whether a variable has been declared:
    > declared undeclaredVariable
    false
    
    > var declaredVariable;
    > declared declaredVariable
    true

Checking that a value is neither undefined nor null

Problem: If you want to check whether a given value is or isn’t defined (neither undefined nor null) then you face typeof’s most “famous” quirk (which is considered a bug): typeof null is "object":
    > typeof null
    'object'
Better: Don’t use typeof for this task, perform this check as follows:
    function isDefined(x) {
        return x !== null && x !== undefined;
    }
Another possibility is to introduce a “default operator” that returns defaultValue if myValue isn’t defined:
    myValue ?? defaultValue
The above expression is equivalent to
    (myValue !== undefined && myValue !== null) ? myValue : defaultValue
Furthermore,
    myValue ??= defaultValue
is an abbreviation for
    myValue = myValue ?? defaultValue
When you access a nested property such as bar, you would want help from an operator, as well:
    obj.foo.bar
The above expression will fail if either obj or obj.foo is not defined. An operator .?? could make the above expression safe by returning the first undefined or null it encounters while traversing the path of properties:
    obj.??foo.??bar
The above is equivalent to:
    (obj === undefined || obj === null) ? obj
        : (obj.foo === undefined || obj.foo === null) ? obj.foo
            : obj.foo.bar
Checking for definedness will become somewhat less important with default parameter values in ECMAScript.next.

Distinguishing between objects and primitives

The following function checks whether x is an object:
    function isObject(x) {
        return (typeof x === "function"
                || (typeof x === "object" && x !== null));
    }
Problems: The above check is complicated by the fact that typeof considers functions different from objects and that typeof null is "object".

Better: The above function is a pretty good solution. The following way of detecting an object is also frequently used:

    function isObject2(x) {
        return x === Object(x);
    }
Caveat: You might think that you could use instanceof Object here. But instanceof uses the prototype of an object to determine whether the instance-of relationship holds and you can create an object that doesn’t have a prototype ():
    > var obj = Object.create(null);
    > Object.getPrototypeOf(obj)
    null
obj is indeed an object, but not an instance of anything:
    > typeof obj
    'object'
    > obj instanceof Object
    false
You’ll rarely encounter this kind of object in practice, but it can exist and there are uses for it.

What is the type of a primitive value?

typeof is the best way to determine the type of a primitive value.
    > typeof "abc"
    'string'
    > typeof undefined
    'undefined'
Problem: You have to be aware of the typeof null quirk.
    > typeof null  // beware!
    'object'
Better: The following function is a fix (for this use case).
    function getPrimitiveTypeName(x) {
        var typeName = typeof x;
        switch(typeName) {
            case "undefined":
            case "boolean":
            case "number":
            case "string":
                return typeName;
            case "object":
                if (x === null) {
                    return "null";
                }
            default: // fall through from prev. case
                throw new TypeError("Argument isn’t primitive: "+x);
        }
    }
Best: It would be great to have a function getTypeName() that performs the above algorithm for primitives and returns the value of the internal [[Class]] property for objects. [2] shows how such a function can be implemented.

Is a value a function?

typeof can be used to check whether a value is a function.
    > typeof function () {}
    'function'
    > typeof Object.prototype.toString
    'function'
In principle, instanceof Function is another way of performing this check. At first glance, it is more elegant. But browsers exhibit a strange quirk: Each frame and window has its own global variables. Hence, if you pass an object from one frame to another, instanceof ceases to work, because the right-hand side refers to a different constructor. That is why ECMAScript 5 has Array.isArray(). It would be nice to have a cross-frame mechanism for checking whether an object is an instance of a given constructor. The aforementioned getTypeName() is one possible work-around, but there might be a more fundamental (as in: cleaner) solution.

Summary

The following would be the most valuable additions to JavaScript, taking on some of typeof’s current responsibilities:
  • isDefined() (e.g. as Object.isDefined()): either as a function or as one or more operators (#2)
  • isObject() (#3)
  • getTypeName() (#4)
  • A cross-frame mechanism for checking whether an object is an instance of a given constructor (#5)
Use case #1, checking whether a variable has been declared, is probably not important enough to warrant its own operator.

References

  1. JavaScript values: not everything is an object
  2. Improving the JavaScript typeof operator

No comments: