isFunction() or isObject(), that is the question ?

In my previous post, we have established few simple rules and presented a simple mechanism for dealing with object “roles” in JavaScript. In the same time we have consluded that isFunction() and isObject() will not be that “trivial” to implement in IE.

Here is the simple requirement: write a function that will return true if argument given is a function. Hint: In IE, typeof window.alert , returns “object”

Here is my portable isFunction() :

// MIT(c) 2009 by DBJ.ORG
// as described here: http://en.wikipedia.org/wiki/MIT_licence
// V.1
var isFunction = typeof (top.alert) == "object" ?
 function(x) {
   case "function" : return true ;
   case "object"   : return (x + "").match(/function/) !== null ;
   default    : return false ;
  }
  :
   function (x) {
   // x instanceof Function; does not work
   // in cross frame situations, so
   Object.protoype.toString.call(x) === "[object Function]" ;
 }
// test
isFunction( window.alert )
// returns true, in all browsers

It works all the time and everywhere. Everybody enjoy? How about passing it this :

// this is breaking the V.1
window.test = {  toString: function() { return '[function]'; } };

Well … above version does not work that well. Here is the (hopefully) proper version :

// V.2
function isFunction (x) {
  switch(typeof x) {
    case "function" : return true ;
     case "object"   :
      if ( "function" !== typeof x.toString )
         return (x + "").match(/function/) !== null ;
      else
        return Object.prototype.toString.call(x) === "[object Function]" ;
      break ;
       default    : return false ;
   }
}
isFunction( test ) /* false */
isFunction( isFunction ) /*  true */
isFunction( confirm ) /* true */

Is this little “hack” breakable ? Well it is, if given this :

var breaker = {valueOf:function(){
      return "[function]"},toString:null
};

Oh well , how about this new version then :

// V.3
function isFunction (x) {
     switch(typeof x) {
     case "function" : return true ;
     case "object"   :
       if (
          ( "function" !== typeof x.toString ) &&
          ( "function" !== typeof x.valueOf )
          )
       return (x + "").match(/function/) !== null ;
      else
       return Object.prototype.toString.call(x)
             === "[object Function]" ;
      break ;
       default    : return false ;
   }
}
isFunction(breaker)
/* returns: false */

Is this it then? The wholy grail of javascript? An universal isFunction() ?
No it is not .. yet. Above (x + "")will fail if this is given as an argument to isFunction(x) :

// breaks V.3
var breaker = { valueOf : null , toString: null }

So, here we go again, with yet another version :

   // V.4
   // universal isFunction for every situation ?
   isFunction = function(x) {
      if ( ! x ) return false ;
       var rx = /function/, ft = "function";
         switch (typeof x) {
           case ft: return true;
            case "object":
              if ((ft !== typeof x.toString) &&
               (ft !== typeof x.valueOf))
                 try { return rx.test(x); }
                  catch (x) { return false; }
             else
                 return Object.prototype.toString.call(x)
                        === "[object Function]";
             break;
            default: return false;
       }
    };
var test = [
  { valueOf: null, toString: null }, false,
  { valueOf: function() { return "function"; }, toString: null }, false,
  XMLHttpRequest, false,
  JSON, false ,
  alert, true ,
  Function(), true,
  null, false,
 undefined, false
],
// passed if : isFunction(test[j]) === test[j+1]
// where j in [0,2,4,6,8,10,12,14]
s = "test";
for ( var j = 0 ; j < test.length ; j ++ )
{
     s += "n" + j + " : " + (isFunction( test[j] )) ;
     j += 1 ;
}
/*
test
0 : false
2 : false
4 : false
6 : false
8 : true
10 : true
12 : false
14 : false
*/

Of course here I am concerned primarily with the solution.
For the above optimizations are certainly possible and are left as an “excersize to the reader” ;)

At last…?

Wait there is more … Verion 5. Seems slightly ridiculous, but it also seems to work (almost) perfectly.

// V.5 --- almost perfect ?
function isF ( f ) {
try { return /^s*bfunctionb/.test(f) ;
} catch (x) { return false ;
}
}

See the comment bellow explaining the single case which brakes V.5. In any case, we have a solution for determining if object is a function (aka “callable”). which leaves yet another “tough cooky” for IE “side” : isObject(). Which will return trues or false , if argument given is object or not. So in the IE this must be false :

dbj.isObject( alert ) ; // must return "false" in all browsers

With the help of isFunction() we can build isObject(), for IE, that will also work properly. We will simly check first if argument is a function. If it is it can not be object in the same time. Here is the working code :

// MIT(c) 2009-2012 by DBJ.ORG
// MIT is described here: http://en.wikipedia.org/wiki/MIT_licence
(function(tos) {
var fs_ = tos.call(function() { }), /* function signature */
os_ = tos.call({}); /* object signature */
dbj.isFunction = ("function" === (typeof window.open)) ? function(f) {
///<summary>
/// isFunction V.5
/// does not handle properly only one case and only in IE
/// var singularity = { toString: undefined,
/// valueOf : function(){return "function";}}
///</summary>
return fs_ === tos.call(f);
} :
function(f) {
// IE version is less trivial since in IE dom and
// browser methods are of a type "object"
// "object" === typeof window.alert
try {
return /bfunctionb/.test(f);
} catch (x) {
return false;
}
};

dbj.isObject = ("function" === (typeof window.open)) ? function(x) {
return (os_ === tos.call(x));
} : function(x) {
// In IE we have to take care of the dom and browser objects being of a
// "object" type. So we have to check first
// (in IE only) dbj.isFunction(x)
if (dbj.isFunction(x)) return false;
return (os_ === tos.call(x));
};

})(Object.prototype.toString);

Update 2012 Oct 08
Please consider this code released under the MIT licence.


NOTE: this post was also inspired with: http://webreflection.blogspot.com/2009/08/isfunction-hacked-iscallable-solution.html

7 thoughts on “isFunction() or isObject(), that is the question ?”

Comments are closed.