4
\$\begingroup\$

In a complex library I've been working on, I got pretty tired of all of the individual type functions and operators to determine whether I was working with an object or piece of data of a given type. This was especially true when I was checking for inherited prototypes between classes. In an effort to alleviate, I made this generalized is() function.

( function( root ) {
    var is = function( v, t ) {
        if ( typeof v !== 'undefined' ) {
            if ( typeof t === 'string' )
                return typeof v === t;
            else if ( typeof t === 'function' ) {
                if ( typeof v === 'object' )
                    return v instanceof t;
                else if ( typeof v === 'function' )
                    return t.prototype.isPrototypeOf( v.prototype );
                else return false;
            }
        }
        return false;
    };
    root['is'] = is;
}( this ) );

Syntax

  • is(variable, type) where type is any return from typeof
    • returns true if variable typeof === type
  • is(variable, class) where variable is an object and class is a constructor
    • returns true if variable is an instanceof class
  • is(class1, class2) where class is a constructor and class is a constructor
    • returns true if class1's prototype is a descendant of class2's prototype.

Usage

I use this function whenever the type of a value is imperative. One common example is when I need different behaviors depending on the type. As an example:

// typeof myVar === 'number'
if(is(myVar,'number')) {       
    doSomething();
}
//      myVar instanceof Array
elseif (is(myVar,Array)) {     
    doSomethingWithArray();
}
//      typeof myVar === 'object'
elseif (is(myVar,'object')) {  
    doSomethingWithObject();
}

Reasons for this function

The goal is to reduce the amount of typeof, instanceof and isPrototypeOf statements, as well as the need for individualized type checking methods. This certainly seems to accomplish much of that goal. A nice little beneficial side effect is that it reduces the code of using libraries significantly. I've been utilizing it in some newer libraries (at a minified overhead of 150 bytes) and it has saved me much more than that, as well as an uncountable number of keystrokes.

Reason for this Question

While I haven't noticed a performance hit, I would like a review with more quantifiable statistics. Additionally, should I have special handling for falsey values? Are there other gotchas that I'm not accounting for?

Here is the minified code:

(function(c){c['is']=function(a,b){if("undefined"!==typeof a){if("string"===typeof b)return typeof a===b;if("function"===typeof b){if("object"===typeof a)return a instanceof b;if("function"===typeof a)return b.prototype.isPrototypeOf(a.prototype)}}return !1}})(this);

Update: Performance

After performing some of the suggested changes, since no one addressed performance, I took some time and learned me some jsPerf. The switch/case variant, performed much more slowly than the if/else variant. Further, I made another variant using the ternary (?) operator which performed much better. Here is the test.

Since type-checking is generally quick and fairly uncommon compared to many other operations, I'm certainly willing to take the hit.

Update: Revised Code (using comments and answer)

( function( root ) {
// To force minifiers to ignore function name
    root['is'] = function(value, type) {
        if ('undefined' !== typeof value) {
            switch (typeof type) {
            case 'string':
                return typeof value === type;
            case 'function':
                switch (typeof value) {
                case 'object':
                    return value instanceof type;
                case 'function':
                // Account for default behavior
                    return type === Function 
                         ? true 
                         : type.prototype.isPrototypeOf(value.prototype);
                }
            }
        }
    // Account for passed undefined values
        return ('undefined' === t) 
             ? true
             : false;
    };
}( this ) );
\$\endgroup\$
  • 3
    \$\begingroup\$ Could you give an example of how this code would be used in practice? Usually, JavaScript programmers don't encounter the is(v, t) problem because duck-typing is the norm. \$\endgroup\$ – 200_success Nov 6 '13 at 23:53
  • \$\begingroup\$ Added in edit above \$\endgroup\$ – Fuzzical Logic Nov 7 '13 at 1:49
  • 1
    \$\begingroup\$ I know this is intentional behavior, but consider whether you want is(v, Array) to return true when v is an array, but is(v, Function) to return false when v is a function and is(v, Number) to return false when v is a primitive number. (is(v, 'function') and is(v, 'number') work correctly, though.) Also, is(v, 'undefined') can never return true. \$\endgroup\$ – icktoofay Nov 7 '13 at 4:35
  • \$\begingroup\$ There is no way to resolve is(v,'undefined') as calling v when it is undefined results in an runtime error. The others, however, I should consider, thank you. \$\endgroup\$ – Fuzzical Logic Nov 7 '13 at 9:42
  • \$\begingroup\$ @icktoofay is behavior different between 'function' and Function, or between 'number' and Number? \$\endgroup\$ – Fuzzical Logic Nov 7 '13 at 9:59
4
\$\begingroup\$

Code Review

Coding Style

You coding style is quite uncommon. However, it is fine if it is consistent across your projects.

My personal preference is to use constant ahead of comparison so it looks a little bit less confusing:

if ('function' === typeof v)

Anyway, in this particular case it is sematically better to use switch so it emphasises that the variable is immutable within the statement:

switch (typeof t) {
case 'string':
    return t === typeof v;
case 'function':
    ...
}

Some style guides also recommend to remove redundant else after return. This will decrease indentation and simplify the code statements.

It is better to use a little bit descriptive variable names. For example, type instead of t and value instead of v.

You can elliminate the variable is by assigning the function directly to root's property. Or there is a special preference to keep the variable is?

Logic

It is doubtful whether this function should return false for is(undefined, 'undefined'). It looks like it is a completely valid check that will fail.

If the argument value is not supported, it may be better to throw an exception instead of returning plausible value. For example, is(null, null) will return false, but null is just not supported value of the parameter t.

I understand that it contradicts a little with an original purpose of the function but it looks like this usage of the function may be quite handy.

Please also be aware of multi-instance environment. If Array object created within one vm instance (or iframe), instanceof Array will fail. You can find more examples here: http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/

Revised Code

(function (root) {
    root.is = function (value, type) {
        if ('undefined' !== typeof value) {
            switch (typeof type) {
            case 'string':
                return type === typeof value;
            case 'function':
                switch (typeof value) {
                case 'object':
                    return value instanceof type;
                case 'function':
                    return type.prototype.isPrototypeOf(value.prototype);
                }
            }
        }
        return false;
    };
}(this));

BTW, its minified version is 8 bytes shorter (with closure-compiler)

\$\endgroup\$
  • \$\begingroup\$ Thank you for the comments. Some notes: I never feel uncomfortable looking at other projects. I've been doing this for 20 years, but am new simply to JS. Regarding undefined, 'undefined' I shall amend that because it is valid. null, null, I do agree, should probably return an exception. What do you mean about your statement on Arrays? could you elaborate or give a use case? Regarding variable names, understood. I often use descriptive unless it is painfully obvious, and when it isn't supportive doc (like above) is always provided. Thanks again! \$\endgroup\$ – Fuzzical Logic Nov 7 '13 at 9:49
  • \$\begingroup\$ One note: is it preferrable to use switch/case when there are only two cases? I've often leaned toward if/else with few cases. I'd like to see if some other pipe in, but this is a handy candidate for an accepted answer if you can elaborate on the Arrays in multi-instance environment. \$\endgroup\$ – Fuzzical Logic Nov 7 '13 at 9:50
  • \$\begingroup\$ I'm sorry for the first statement. Just corrected it. As for Array here is an example: perfectionkills.com/… You may do not use the function is in this way, of course, so it is fine now, but it is very interesting aspect of the instanceof. As for switch it is, of course, very subjective, but there is at least one important drawback of if in such case: the value should be calculated twice so it is a code duplication. Very small, but here it is. \$\endgroup\$ – Alex Netkachov Nov 7 '13 at 11:31
  • \$\begingroup\$ Thank you very much for that link. I would still prefer that you place it in your answer, but this is certainly good enough for an accepted answer. While your switch does not perform well for this function, your justification regarding usage is sound. I will be providing edits as I gather more information, I encourage you to do the same should you feel the need. \$\endgroup\$ – Fuzzical Logic Nov 7 '13 at 13:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.