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() ans 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. Note: In IE, typeof window.alert , returns “object”

Here is my portable isFunction() :

// GPL (c) 2009 by DBJ.ORG
// 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. Enjoy …? How about passing it this :

// this was breaking the previous version
window.test = {  toString: function() { return '[function]'; } };

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

// 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” unbreakable ? 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) :

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. Slightly ridiculous, but it also seems to work (almost) perfectly.

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

See the comment bellow explaining the single case which brakes this. 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 :

(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 /\bfunction\b/.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);

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

This entry was posted in Development, IT, javascript and tagged . Bookmark the permalink.

6 Responses to isFunction() or isObject(), that is the question ?

  1. alert(isFunction({toString:function(){return “function”}}));
    // true in every IE

    As already discussed, the Object.prototype.toString.call(x) === “[object Function]” is the best way so far to know if that function is usable as every other function. In IE only you can have problems with native function so you would like to know if these are IE native function and again, as already posted, this is my snippet:


    function isIENativeFunction(f){
    return !!f && typeof f.toString === "undefined" && /^\s*\bfunction\b/.test(f);
    };

    alert(isIENativeFunction(alert));

  2. Dusan says:

    Sorry Andrea, but your “solution” will not work with this argument :

    
    isIENativeFunction(
    { toString : undefined, valueOf : function () { return "function X";} }
    )
    /*
    returns: true
    */
    

    And it will throw an exception in this case :

    
    isIENativeFunction({ toString : undefined })
    /*
    TypeError
    Number : 438
    Description : Object doesn't support this property or method
    */
    

    Etc … I am sure, by now, you have spoted the flaw in your logic. But thanks for your comment, anyway.

  3. Dusan says:

    There is a solution , which *should* work “everywhere, and which fails on *only* one singularity.
    There is only one “bomb” which is hardly possible in reality, unless someone “plants” it deliberately. For *every* other case this simple function bellow should work.

    
    var bomb = { toString : undefined, valueOf: function(o) {return "function BOMBA!"; }} ;
    // V.5    --- almost perfect ?
    function isF ( f ) {
       try {  return /^\s*\bfunction\b/.test(f) ;
       } catch (x) {   return false ;
       }
    }
    
    isF(bomb)
    /*
    true  -- not OK
    */
    isF(alert)
    /*
    true  -- OK
    */
    isF( isF )
    /*
    true -- OK
    */
    
  4. Pingback: 修复jQuery中isFunction方法的BUG | My Sky

  5. Pingback: 修复 jQuery 中 isFunction 方法的 BUG | 前端开发吧

  6. Pingback: 修复 jQuery 中 isFunction 方法的 BUG - 隐遁峰

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>