My JavaScript book is out! Don't miss the opportunity to upgrade your beginner or average dev skills.

Wednesday, August 05, 2009

isFunction hacked, isCallable solution

After this post, and after some twit exchange with @kangax, and finally after my hack for my function itself, here I am with my definitive solution to know if a passed argument could be called.

var isCallable = (function(toString){
// The Latest WebReflection Proposal
// Recently Updated with "explicit" cast
// thanks to abozhilov for the test case
var s = toString.call(toString),
u = typeof u;
return typeof this.alert === "object" ?
function(f){
return s === toString.call(f) || (!!f && typeof f.toString == u && typeof f.valueOf == u && /^\s*\bfunction\b/.test("" + f));
}:
function(f){
return s === toString.call(f);
}
;
})(Object.prototype.toString);

Speed will be the same with every browser, IE will do some extra check only if passed argument is not a function.
The "native" case could be a performance greedy check and we do not like eval in any case.
The "isFunction" name is not appropriate, cause a function is not a Host Object, as is effectively alert, as example, in Internet Explorer.
isCallable simply gets the best from old proposal, threading edge cases in a specific way for the edge browser, Internet Explorer.
Enjoy!

44 comments:

kangax said...

This will fail if `u` is declared before the snippet with non-`undefined` value, will it not?

Andrea Giammarchi said...

obviously not, JavaScript is left to right language and "var u" is before typeof u ;)

DBJ said...

This is what I mean by "obfuscation" Andrea. Kangax is very experienced javascripter and still even he got confused with your obfuscation:

var u = typeof u ;

this works but is not necessary when showing any solution. Key approach , so that your readers understand you is:

1. solution
2. possible optimization

you jump straight to 2, and loose 99% of your readers. So "obfuscation" is "unnecessary complication" ...

Your good solution should be fully and clearly explained first, before optimizing it:

(maybe something like this)

// ECMA internal property "Class" is provided by
// Object.prototype.toString.call(o)
// ... here more conceptual explanations ...
var isCallable = (function(class_provider){
// here perhaps a link to the explanation what is a "closure" , etc ...
var s = class_provider(Function()), // why do we keep "[object Function]" ... and more explanations ...
u = undefined;
return typeof this.alert === "object" ? // here explain why this check, IE ... etc ...
// IE versions
function(f){
return s === class_provider(f) ||
(
!!f && // here perhaps a link to: "why the !! construct" article
typeof f.toString == u && // we do this check because of ... etc ...
typeof f.valueOf == u && // we do this check because of ... etc ...
(f+"").match(/function/) !== null
// this regexp tests for the existence of the
// first word "function" in the decomposition
// of the function source code achieved by
// explicit coercion of 'f' to the string
// this is same as /function/.test(f)
// which will be used in the optimized version
);
}:
// non IE browsers and all browsers where DOM methods are seen as "function"
// by JavaScript
function(f){
return s === class_provider(f);
}
;
// here we provide argument to the closure call
})(Object.prototype.toString.call);

I hope you understand better now, what I mean by "obfuscation" ;o)

Andrea Giammarchi said...

DBJ I guess this blog is not for newbie ... you forgot to say why I used var before a variable declaration but in any case I already explained everything the precedent linked post.

I bet kangax just did not think about left2right parsing and he complained already about my vicious code-style.

In this case I simply used language features to obtain shortcuts and avoid static unoptimized strings removing as example "[object Function]" and "undefined" from the possible resulted gzipped/inflated/packed dictionary. The result? My code once optimized does not increase the global size cause I am always focused on reused variable or functions name and common operations ;)

DBJ said...

Urgh, sorry my "non obfuscated" version generates (of course) the :
"Cannot convert WrappedNative to function" , exception ...
I should have passed : Object.prototype.toString, to the closure ...

Although the point was to show how an solutions should look, before it is optimized ... not to post perfect code ;o)

DBJ said...

It is very simple Andrea:

First: solution
Second: optimizations

All the best programming books are written with this in mind. Same as all the academic (or not) papers containing programming language source code.

Also, I can assure you, a lot of well written commercial software , is written in very simple style. Without any optimizations. Even system stuff.

Andrea Giammarchi said...

I do post perfect code, you comment it ... easy? :D

Jokes a part I linked the other post because everything is there.

So, you want the optimized solution? This post ... you want to understand it? Precedent post ... still easy, imho.

DBJ said...

Call me a pest but I will again insist, I also have a "solution" , for isFunction problem:

http://dbj.org/dbj/?p=286

I think when "optimized" it might look very similar to your perfect one ?

Where we disagree is that I would gladly use mine, in its present form. Happily and everywhere ?

Andrea Giammarchi said...

mate, you can do whatever you want, I write my code here and my proposal. Nobody has definitive solution in JS, algorithms a part.

P.S. I hacked your callback, would be nice to write people whose helped you to find your solution, as I did with kangax about the name. I don't mind in any case while your link is here in my comments ... regards

Andrea Giammarchi said...

P.S. DBJ, your code is so cool that with a truly common test case it will raise an exception.

isFunciton(null)

Moreover, your code is much bigger than mine without a single benefit about its size plus a try catch that will slow down it consistently.

Still, feel free to think your code is the solution. I am just a developer, I have a problem, I solve it if anybody else solved it before and better than me. That's it. Regards

DBJ said...

Rectified ...

Jake Archibald said...

I have to agree with DBJ. If shortcuts are used they should be commented for future readers (even your future self may benefit from this). Let a minifier and gzip handle the bulk of the compression, there's no point in having single letter identifiers.

If you're looking for performance, you should replace your regular expression with indexOf.

Jake.

Andrea Giammarchi said...

I said already, I won't reply. Every comment is in the other post, the first link in this one. If you are lazy to click next time I will update a post rather than create a new one so problem solved 'cause as I have said everything is explained there.
indexOf requires a manual ("" + f) plus a variable to store it plus two compared values 0 or 1 to obtain the same result. Not sure it is faster.
At the same time, the second check will be performed in IE only if the first one did not pass so it is an edge case itself and performances are 5 times better than DBJ proposal, for example. Just test it widely and you will see.
Finally, f for function, s for string, u for undefined, I wonder you guys how many time waist writing absolutelyMeaningFulVariableName in3LinesOfCode ... I do not, sorry. DBJ already commented the code in any case, so if something is not clear I will clarify.

J5 said...

Code looks great to me, Andrea. Everything it needs to be and nothing more. Thanks for sharing your thoughts with all of us.

Jake Archibald said...

Hmm, your attitude to questions and suggestions about your code comes across really badly. You don't need to take it as a personal attack and attack back. It comes across like a teenager who's just heard an negative comment about their favourite band.

In reply to kangax, you start "obviously not", suggesting they are silly / stupid for not noticing in the first place. Now, it could be the case that kangax is stupid (I suspect not), it could be that they simply misread your code, it could be that they're proving your code isn't that obvious at all. Regardless of that, "obviously not" serves only to make you look arrogant and doesn't help anyone.

In reply to DBJ you once again resort to attacking, suggesting code unlike yours is for newbies. Once again this sounds massively arrogant, reminiscent of The Emperor's New Clothes. You then sarcastically poke fun when you find an error in his code. Is this helpful to anyone? If the intention behind your blog is to inform, help and educate others, why be so unhelpful in the comments?

Believing oneself to be infallible is not the way forward. Listen to suggestions, if you think they're wrong, explain why. When it comes to opinion, debate is healthy and should be encouraged, not beaten down with sarcasm.

Andrea Giammarchi said...

Jake, it is quite obvious you completely missed the background behind this post, which was to explain to DBJ that his tentative was not good, specially after a solution was already provided by me and others before, jQuery library itself which was more reliable than first 4 version DBJ posted in its blog even if versions are more than 5, trust me.
Summary here, which is the second "flame" about the problem. This is the first one.
As you can spot there, DBJ even declared Mr Crockford was going to comment something currently old, threaded dunno how many times, and with more than a better solution than DBJ proposed one, which posted un-tested code which failed every time after 5 minutes.
About kangax, trust me, He knows better than me that expression would have certainly produced undefined.
My point is that I do not like flames that much, even if sometimes I am in pole position to create one ... but after a while I get bored and DBJ with his "not listening" way could comment with details whatever he wants even it keeps not working.
Arrogance? I guess you discovered WebReflection just recently, right? Well, I hope you'll come back, if interested, to understand that I always take care about suggestions, but only if reasonable and good one. That's how I learned what I know about programming and web developing, whatever language it is.
Best Regards

Andrea Giammarchi said...

P.S. I prefer the Emperor's New Groove, from Walt Disney, too hilarious!

Jake Archibald said...

Ok, I may have got the wrong impression but it's something I've noticed in a few posts.

Anyway, back to JavaScript...

var u = typeof u;

Although left-to-right is a good way of explaining the above, the order of the declaration and the initialisation doesn't really matter to JS, declaration will always happen first. Eg:

var msg = "world";
function alertHello() {
msg = "hello";
alert(msg);
if (0) { var msg; }
}
alertHello(); // "hello"
alert(msg); // "world"

Andrea Giammarchi said...

var declaration, as function creation, are resolved before everything else. The "left to right" I was talking about is about typeof u, where u is already defined because declared as a var in the scope, returned to u, the var itself. Your example shows declaration parsing priority, but does not explain the logic behind var u = typeof u; where left to right is the assignment. Could have been typeof u; var u; and if it was like that, DBJ woul dhave been correct about obfuscation. That is why I think there is no obfuscation at all in my code.

Pauan said...

I found it confusing as well.

Not because I don't understand left-to-right or anything like that, but simply because "typeof u" makes no sense (until I thought about it for a while).

If it had been "u = typeof undefined;" I would have gotten it right away.

Of course "u" is undefined, because you haven't assigned a value to it. But to outsiders looking at your code, it's very confusing. "typeof undefined", on the other hand, is not.

My suggestion would be to place a comment, stating that it's the same as doing "typeof undefined". That comment would go a long way toward better understanding.

Other than that one issue, it looks good. Just my 2 cents.

P.S. I'm guessing you did "typeof u" rather than "typeof undefined" in case somebody decided they wanted to overwrite the value of "undefined"?

Andrea Giammarchi said...

exactly, undefined is a non-safe check. that is like var undefinedType = typeof undefined;

Regards

DBJ said...

http://dbj.org/3/eval.htm

We both have, different versions.
Each err-ing on its own singularity.
Only in IE, of course...

And. These both will "almost" work if function decomposition behaves "everywhere". Which somehow I do, not believe, same as http://thinkweb2.com/projects/prototype/those-tricky-functions/

Andrea Giammarchi said...

considering that an Object like this: { toString: undefined, valueOf: undefined }
is both user created and something nobody will never use in a real case scenario, I am pretty much happy about the error results which aim is to block possible malicious objects to pass a function which aim is to make something callable for sure. In few words, aim reached, that object is not callable, which is better than a false positive, imho. If you use Function.prototype.call(HostObject) you could obtain same error in IE so I guess it is ok. Thanks in any case for that page test. Regards

abozhilov said...

Andrea one tips:
Native method and host function in IE, throw's error when you try to attach new property.

abozhilov said...

Try it yourself my solution, without RegExp:

//IE isCallable
Object.isCallable = function(c)
{
if (!c) return false;
if (c instanceof Function)
{
return true;
}
try {
c.__call = c.__call;
return false;
}catch(e)
{
return true;
}
}

Andrea Giammarchi said...

abozhilov I wonder if rather than "just post" you read my code and tried it. It works with everything and I am not attaching anything. If not, please tell me a single case where my isCallable fails, thanks.

Finally, I hope you are not just yet another new webreflection discoverer ... but you are acting like one.

Regards.

abozhilov said...

Adrea,
Try it please:
isCallable(new ActiveXObject('Microsoft.XMLHTTP'));

This is harmful for my application...

abozhilov said...

Now you tell me, my bugs :~)
//IE isCallable
Object.isCallable = function(c)
{
try {
return c instanceof Function || !!(c.__t__ = c.__t__ && false);
}catch(e)
{
try {
return !!String.prototype.indexOf.call(c);
}catch(e){};
return false;
}
}

Gived one simple case, where i broke your browser or return strange value.
And i don't attach new property or values :))

abozhilov said...

Sorry, i forget one 'null' in last code:
//IE isCallable
Object.isCallable = function(c)
{
try {
return c instanceof Function || !!(c.__t__ = c.__t__ && false);
}catch(e)
{
try {
return !!String.prototype.indexOf.call(c, 'null');
}catch(e){};
return false;
}
}

So, why you so arrogant? I never go back here... To read your "perfect" code...

Andrea Giammarchi said...

abozhilov, I have already fixed the function and now I tell you why my bored answer OK?

You pretended with a comment to tell me that my function is crap because "Flanagan befre ... and attached properties after"

Both replies did not make sense and I was not attaching anything indeed.

Your solution is not a solution, it is not testing the fact the object is callable, it is excluding a possible internal property from a prototype chain (double underscore as prefix/postfix is usually genuinely reserved for browsers) and it is extremely slow for both try catch usage plus zero in-closure shortcuts.

So, what we discovered today?
That I simply forgot an explicit cast for weird cases (but thanks for your test case, that was sufficient to understand my function real problem) and that you had no idea why my function generated an error, but you had in any case to blame something I've never done before - attached properties - and me after, acting arrogant, pretending I am the one.

Finally, you did exactly the same error somebody did a while ago in jQuery dev list, replying without understanding the code.

You want to come back with a different approach? You'll be more than than welcome!
You don't? Is not gonna be a massive lost for WebReflection if this is the way you suggest bugs or propose alternatives.

Regards

abozhilov said...

"Your solution is not a solution, it is not testing the fact the object is callable, it is excluding a possible internal property from a prototype chain (double underscore as prefix/postfix is usually genuinely reserved for browsers)"

Yes reserved for browsers. In your "solution", prototype chain is visible for all. And after this fact.
Whats happen, if i override test method of RegExp???? I broke your perfect code..... Because everything in RegExp.prototype is public. What is the Regular Expression concept? Maybe for testing type of object?!?

Please, don't tell me, how i don't understand your "perfect" code. OK? You are another JavaScript "Guru", who tell us. Looked my code is "Capo di tutti capi".

In conclusion, yours solution and my, is workaround on IE bug's. Do you guarantee for your code? Do you thing your code works well with everything native method and object in IE?

Andrea Giammarchi said...

abozhilov you cannot be serious, I am starting to think you are DBJ with a fake name.

You completely "jumped" the part where your blaming analysis was wrong, as wrong is your solution.

First error:
c instanceof Function
With sandboxes (frames and iframes) isntanceof will always fail. You do not deeply understand the problem but you pretend to have a solution.

Second error:
!!(c.__t__ = c.__t__ && false)
you are assigning an obtrusive __t__ property to every object passed for your "solution" and you do not know that !!c.__t__ would have produced the same result in that expression. I call this bad code design.

Third error:
You are acting in a hilarious way.
Nobody said JavaScript is secure, JS beauty is its malleability indeed.
If your point is that some idiot could redefine the RegExp.prototype.test method how can you propose this as fallback for your test?
return !!String.prototype.indexOf.call(c, 'null');

Please stop to embarrass yourself and consider that if I write or do some mistake I am ready to learn something new ... which is the difference between me and you I guess.

Best Regards

abozhilov said...

First error: instanceof
This is not error. In sandboxes Function is complete different object. Allright, i have question for you. Whats happen if you defined.
Function.prototype.new_member;
In sandbox we don't have, new_member. That say, this two object is complete different. And instanceof is best way for safe application from error in this case.

Second error: c.__t__ = c.__t__
In JavaScript if object doesn't not have that property __t__. Return undefined. In my code this safetly operation. If __t__ have any value, not changes.
I don't attach anything! Understand?

Third error: Public prototype.
Yes. Here you right.

Andrea Giammarchi said...

The best way is toString from Object.prototype applied to a possible callback to know if it returns "[object Function]"

Secondly:

var o = {};
o.__t__ = o.__t__;
for(var k in o)
alert(k); // will alert __t__


you do not know JavaScript at all man, I already said do not embarrass yourself.
I am off man, but you can carry on by yourself if you like, bye.

abozhilov said...

Oh my good... You don't understand basic Object oriented concept and you tell us how to write code... Don't do this please.
Simple example with dummy exec method.

Function.prototype.exec = function()
{
this.call();
}

function isFunction(sender)
{
return Object.prototype.toString.call(sender) == '[object Function]';
}

function tryThis(sender)
{
var a = sender.contentWindow.alert;
if (a instanceof Function)
{
a.exec();
}
if (isFunction(a))
{
a.exec();
}
}

What about this? Tell us, why in the second case when i use isFunction, my browser throw error?
And instanceof is the best way to check type of object. BB, and sometime is good idea thinking about problem. Not only copy/paste, that write Doug, Jhon or anything else...

abozhilov said...

Hm, this is shit.
Here JavaScript interpreter not optimize this operation. Ok one delete and we are ready :)))

Andrea Giammarchi said...

listen, after this:

In JavaScript if object doesn't not have that property __t__. Return undefined. In my code this safetly operation. If __t__ have any value, not changes.
I don't attach anything! Understand?


which demonstrate how low is your JS level, I have nothing else to say and you neither but you do not learn.
You are a good throll indeed, and I find kinda funny to publish your deliriums, so please go on, you cannot get worse than this ;-)

P.S. as rule, I always publish comments unless there is no spam and no offensive content. This is just WR netiquette. Have fun today, OK? I am going back to work now

abozhilov said...

You are yet another "copy/paste" man. I don't wasting the time, with people like you! I never told. "I know JavaScript"... But you... You are arrogant fool! BB and i hope one day, me and you meeting anywhere.

Andrea Giammarchi said...

copy and paste ... that's why you are here, right?

Ah ah ah, you are the most hilarious wannabe ever! Thanks to make my day :D

P.S. I may result arrogant but for sure I do not post without understanding the code before, commenting it, and proposing solutions just to appear cool and skilled. If there is something I did not consider, I ask, and eventually I learn something, I do not keep bothering without valid reasons.

Best Regards

abozhilov said...

I say you don't guarantee for your code!
What are you thinking about DOM object :)))
Image
Option
Image.create method
Option.create method

Andrea Giammarchi said...

nobody guarantee nothing with JS and I have never said I guarantee something.

This isCallable function simply tell you if you can call a function and after you have no idea how many other discussion about this and everywhere with whoever.

You should read the old post, the jQuery dev discussion, visit the kangax website, understand the problem, be realistic about daily JavaScript environment, and only after this investigation, you can come back and discuss about my function, otherwise we both waist our time, OK?

These are only suggestions, you can take them, you can ignore them keep talking like the last arrived JS dev wannabe, writing or confirming your mistakes and/or typos misunderstanding 9/10 of problems/features/reasons/etc,etc

When you'll start to write something meaningful and pertinent I will reply without problems while If you want to ask something specific I will be happy to reply.

Regards

abozhilov said...

You don't answer my questions. That's enough!

abozhilov said...

:))) You not guarantee "isCallable" return exactly "isCallable" :))) This is joke :))))
Maybe right name on your function is:
"isMaybeCallable"
Oh my good :))))

DBJ said...

DBJ != Abozhilov