So how do you tell if an object is a function?
What's wrong with typeof?
typeof o == "function";
Some browsers have unexpected behavior when using the typeof operator with Host objects.
For example, in Safari, typeof aNodeList == "function"
bug 14547.
Mozilla returns "function" for typeof on RegExp objects (bug 14547), and object elements (bug 268945,
bug 296858).
The instanceof Operator
The more flexible alternative to typeof is instanceof.
return it instanceof Function;
Unfortunately, instanceof will not return the desired result across frames. Each window has a different global object, so Function in frame 1 is not Function in frame 2.
When working across frames, the constructor property and isPrototypeOf will have the same problems as instanceof.
For example, an iframe with a function f, and the following code in the parentWindow:
var i = document.getElementsByTagName("iframe")[0];
var iframeWindow = i.contentWindow;
iframeWindow.f instanceof Function; // false
typeof iframeWindow.f == "function"; // true;
It should be easy to know what an object's type is. Why does JavaScript
make this so hard?
What Do the Libraries Do?
Dojo had the idea of depending on a combination of the userAgent string, the return
value of the object's toString method, and implicit type conversion that happens with the Equals Operator §11.9.3.
if(dojo.isBrowser && dojo.isSafari){
// only slow this down w/ gratuitious casting in Safari since it's what's b0rken
dojo.isFunction = function(/*anything*/ it){
if((typeof(it) == "function")
&& (it == "[object NodeList]")){ return false; }
return (typeof it == "function" || it instanceof Function); // Boolean
}
}else{
dojo.isFunction = function(/*anything*/ it){
return (typeof it == "function" || it instanceof Function); // Boolean
}
}
This is to address Safari's unreliable results when using the typeof operator on one particular host object.
The function will fail in Safari 3 when passed an HTMLCollection (try typeof document.links). It will fail for any other object that Safari thinks implements call.
The problem is not that dojo forgot to check for HTMLCollection. Web developers should not need to know such obscurities.
jQuery : isFunction
The jQuery code stumbles on this problem:
// This may seem like some crazy code,
// but trust me when I say that this
// is the only cross-browser way to do this. --John
isFunction: function( fn ) {
return !!fn && typeof fn != "string" && !fn.nodeName &&
fn.constructor != Array && /function/i.test( fn + "" );
},
Lets break down this crazy code to see what it's doing.
( !!fn ).......................convert to boolean value. null, et c are out
( typeof fn != "string" )......not a string value (String objects pass here)
( !fn.nodeName )...............Not an object with a truthy nodeName property
( fn.constructor != Array )....Not an Array object constructed in this frame
( /function/i.test( fn + "" )..toString contains "function"
Does it work?
// Case 1: testObj is not a function.
var testObj = {
toString: function() {
return "This object contains only one function."
}
};
// Case 2: testString is not a function.
var testString = new String("hey! This is a String, not a function!");
// Case 3: WidgetFactory is a function.
function WidgetFactory( id ){ }
WidgetFactory.toString = function() { return "WidgetFactory"; };
// Case 4 : An Array in the frameWindow is not a function.
// var someArray = [ function(){}, "Simplify functional testing." ];
// Here's the test:
var frameWindow = document.getElementsByTagName("iframe")[0].contentWindow;
isFunction( testObj ); // true.
isFunction( testString ); // true.
isFunction( WidgetFactory ); // false.
isFunction( frameWindow.someArray ); // true, contains "function" (twice).
Doesn't work that well, does it?
Running code doesn't lie. All four tests failed.
The jQuery function will give the correct result sometimes, but not in any of the cases above. This is because jQuery is relying on the result from the function's toString.
These cases are quite obvious and likely cases where the above method will fail. Nothing tricky about it.
The value returned by toString should not generally be relied upon, and especially not with a Host object.
JavaScript forces developers to rely on toString with Date
or Number objects. This is an API design of the language that goes against convention. Such design should not influence a JavaScript library author's design decisions.
The disadvantage of specifying the format of the toString return value is that once you've specified it, you're stuck with it for life.
—Joshua Bloch, quote from Effective Java
Method toString should not, generally speaking, be relied upon.
Instead,
toString should be used for diagnostic messages.
String Objects are Not string Values
Web developers sometimes get confused with string values and String objects. My tutorial on how property access operators work, clearly explains this fundamental concept.
Here's another example from Dojo.
dojo.isAlien = function(/*anything*/ it){
// summary:
// Returns true if it is a built-in function or some other kind of
// oddball that *should* report as a function but doesn't
if(!it){ return false; }
return !dojo.isFunction(it)
&& /\{\s*\[native code\]\s*\}/.test(String(it)); // Boolean
}
Besides conjuring up images of little green men, function
isAlien returns true for anything that isFunction returned false for and also contains "[native code]" in the object's string value. The intent seems to be to check if a function might be a function even when typeof failed.
I have a very hard time believing that any unmodified built-in function does not return "function" for typeof.
The comment needs more detail.
It would be interesting to see the dojo comment proven true. At least they put an explanatory comment in.
The comment implies that a built-in function might not return "function" but does not provide a case where this is true. The code is based on that assumption and assumes that such an object's String value will contain "[native code]".
This approach suffers from the same problems that jQuery's isFunction suffered from. If any object's string value contains "[native code]", isAlien will return true. Granted, "[native code]" is less common than "function", but buying vowels isn't the answer.
Function isAlien provides a way for non-callable objects to slip through as functions. It's inclusive, like the jQuery function, and just as dangerous. It does this by relying on [native code] making deductions about the Object based on its toString's return value.
Return values from toString should not, in general, be relied on
If it walks like a duck and quacks like a duck, it must be a duck.
—
How to Determine if an Object is Really a Function
Functions are unique in a few ways. The return value for toString is not one of them.
It seems safer to introspect the object based on a unique characteristic.
Update - Oct 13
A solution is needed. Here is one more offering, introspecting the constructor property, accompanied with a testcase.
function isFunction(fn) {
if(typeof fn != "function") return false;
if(typeof fn.constructor != "function") return false;
return fn.constructor.prototype.hasOwnProperty( "call" );
}
That's some ugly looking code, huh? Who has a better one?
Try to provide case where the testcase will fail.
We can make more functions for isArray, et c. What a pain in the ass. This should be easy. JavaScript makes it hard.
Type Checking - the Status Quo
JavaScript needs a way to perform equivalence check. This might be helpful for cross frame issues.
JavaScript provides instanceof and typeof, but neither are sufficient
instanceof fails across frames. It doesn't work with primitives, e.g. "foo" instanceof String is false.
The typeof operator has a limited number of return types. typeof doesn't support all of the built-ins properly (e.g. Array, Error, Date, and null are all "object") and doesn't support any user-defined constructors. Typeof is allowed to return an implementation-dependent value for
host objects. Safari conforms, but in an annoying way that causes problems.
| Type |
Result |
| Undefined |
"undefined" |
| Null |
"object" |
| Boolean |
"boolean" |
| Number |
"number" |
| String |
"string" |
| Object (native and doesn't implement [[Call]]) |
"object" |
| Object (native and implements [[Call]]) |
"function" |
| Object (host) |
Implementation-dependent |
Internet explorer includes an additional "unknown".
Typechecking is an area that JavaScript, as a language, needs to improve.
Update...
Brendan just posted on the ES4 List:
The is operator tests universal or Platonic type, which involves
shared, immutable type descriptors that do not vary across windows or
frames. So
(it is Callable)
That looks very useful. Immutable objects are powerful building blocks for other object types. Looks like there might be the possibility of other callable types (or subclass of Function) in ES4.
Technorati Tags:
JavaScript