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() :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// 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 :
1 2 |
// 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 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// 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 :
1 2 3 |
var breaker = {valueOf:function(){ return "[function]"},toString:null }; |
Oh well , how about this new version then :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// 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)
:
1 2 |
// breaks V.3 var breaker = { valueOf : null , toString: null } |
So, here we go again, with yet another version :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
// 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.
1 2 3 4 5 6 |
// 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 :
1 |
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 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
// 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 ?”
[code]
alert(isFunction({toString:function(){return "function"}}));
// true in every IE
[/code]
As already discussed, the [code]Object.prototype.toString.call(x) === "[object Function]"[/code] 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:
[code]
function isIENativeFunction(f){
return !!f && typeof f.toString === "undefined" && /^\s*\bfunction\b/.test(f);
};
alert(isIENativeFunction(alert));
[/code]
Sorry Andrea, but your “solution” will not work with this argument :
[code]
isIENativeFunction(
{ toString : undefined, valueOf : function () { return "function X";} }
)
/*
returns: true
*/
[/code]
And it will throw an exception in this case :
[code]
isIENativeFunction({ toString : undefined })
/*
TypeError
Number : 438
Description : Object doesn’t support this property or method
*/
[/code]
Etc … I am sure, by now, you have spoted the flaw in your logic. But thanks for your comment, anyway.
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.
What’s the reasoning behind this being GPL?
@Aran Thanks for a comment. All of my code is released under MIT licence these days As described here:
http://en.wikipedia.org/wiki/MIT_licence
Consider this also to be MIT licence.
Thanks @Chris … Valid question is:
is that a valid use case ? But anyway here is the solution to “something” calling with this kind of “clever” string:
Version 5 fails on any string beginning with “function”.