6

I'm trying to write a helper method in JavaScript. It should act differently if one sends in a function or an reference to a function.

I want to like to use it like this:

helper('div', function () { return false; })
helper('div', obj.fn)

What I can't figure out is: how to inside the helper function tell the difference between the two?

I think it's due to that JavaScript first evaluates the obj.fn before it sends it in. The only workaround I found is to send the obj.fn as an obj, i.e.

helper('div', { fn: obj.fn })

Then I can tell the difference between the two with typeof. But I really like some way to make it without the extra object declaration.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
fredrik
  • 17,537
  • 9
  • 51
  • 71
  • 2
    Do you have a specific reason, why you want to tell two functions apart? – Boldewyn Dec 17 '09 at 11:13
  • 3
    "It should act differently if one sends in a function or an reference to a function" - what does this mean? How do you define the difference between a "function" and a "reference to a function"? In your first example, the parameter passed to helper will be a reference to a function either way. – Tim Down Dec 17 '09 at 11:55
  • @Tim Down, that confused me too but I think the asker is assuming passing an anonymous function is passing by-value and passing an object method is passing by-reference which is wrong of course but it's a mistake many js developers make. – Andy E Dec 17 '09 at 12:24
  • 1
    I still don't understand what the problem is. It would seem from the accepted answer below that you want to distinguish between named and anonymous functions. If so, why? – Tim Down Dec 17 '09 at 13:17
  • @Tim Down. it's due to that in my helper function one should be able to send in a anonymous function and a declared one. And if it's declared it's should be executed in it's own scoop. So that the this-keyword represents the object the named function is declared in . Since the named function should be able to be executed both via the helper function and by it's own. – fredrik Dec 18 '09 at 12:12
  • 2
    @fredrik: then you should have accepted Andy E's other answer (he posted two). Knowing if a function has a "name" doesn't give you anything. You cannot, *cannot*, execute a passed object function in it's correct scope just by knowing it was the result of `var f =` vs `function f`. Even function expressions can be assigned a named function (`var f = function g(){}`). You absolutely need the object itself. – Crescent Fresh Dec 18 '09 at 17:09
  • 2
    @fredrik: I think you have some misconceptions about functions in JavaScript. What becomes `this` inside the body of a function has everything to do with how the function is called and nothing to do with how it's declared. – Tim Down Dec 19 '09 at 00:58

3 Answers3

2

UPDATED *(AGAIN): I thought that the toString() method might be your only way forward here. It doesn't however treat a reference of an anonymous object differently.

This code demonstrates that:

function acceptparam(fn){

            console.log("fn.constructor = " + fn.constructor);
            console.log("typeof fn = " + typeof fn);
            console.log("fn toString " + fn.toString());

            console.log("fn.prototype = " + fn.prototype);
            console.log("fn.prototype.constructor = " + fn.prototype.constructor);
            console.log("this[0] = " + this[0]);
            console.log("---");

        }

        function empty(){
            return ;
        }

        var x = {
            y : function(){return;}
        }

        acceptparam(empty);
        acceptparam(function(){return;});
        acceptparam(x.y);

Very interesting question, without implementing your own solution I dont think you can do it, this post helps to explain why. Its about the parent child relationship only being one way.

http://codingforums.com/showthread.php?t=134855

Lewis
  • 5,769
  • 6
  • 30
  • 40
  • You were right about toString... look at the console output carefully: `fn toString function empty()` vs. `fn toString function ()` – Skilldrick Dec 17 '09 at 11:21
  • Which is what Andy E's answer is doing :P – Skilldrick Dec 17 '09 at 11:22
  • 3
    Thats not what he's asking for thoguh, which is where I went wrong originally! fredrik is asking for the difference between an anonymous function and an object with a child function, which doesnt work. – Lewis Dec 17 '09 at 11:30
  • @Lewis, that's true to an extent but functions that are properties of an object can be declared as named functions. See my updated answer. – Andy E Dec 17 '09 at 11:44
1

I thought I'd add another alternative answer, mainly because I didn't want to add to the soup that is my other answer but also because it didn't go down to well with the stackoverflow voters that don't leave constructive comments ;-)

As an alternative to what you're trying to do, you could add a third parameter to the helper function:

function helper (tagName, fn, method)
{
    if (method)
        fn = fn[method];

    //- Do rest of helper function here
}
//- Now if we pass an object method to helper function we can identify it properly
helper('div', obj, "fn"); // method is obj.fn
helper('div', function () { blah(); }); // Still works fine

Merely a suggestion and works as well as or even better than your current work-around.

Andy E
  • 338,112
  • 86
  • 474
  • 445
  • Thanks. But actually that's was my first thing I tried. But I wanted to make it as simple as possible. But been messing around with this for awhile now. And it looks like I have to specify the obj that holds the function due to I want then the fn is triggered it should be in the right scoop. So that the this-keyword is refered to obj. – fredrik Dec 17 '09 at 13:32
0

You can use toString() to find out if the function is anonymous assuming it is declared as a named function and not an unnamed function assigned to a variable:

function jim () { var h = "hello"; }
function jeff(func) 
{ 
    var fName;
    var inFunc = func.toString();
    var rExp   = /^function ([^\s]+) \(\)/;
    if (fName = inFunc.match(rExp))
       fName = fName[1];

    alert(fName);
}

Will give you the name of the function if any.

jeff(function () { blah(); }); // alert: null;
jeff(function joe () { blah(); }); // alert: "joe";
jeff(jack); // "jack" if jack is function jack () { }, null if jack = function() {} 

My previous edit referred to an IE quirk that didn't exist in other browsers and is no longer valid in IE as of version 9. However, you can still assign named functions as object properties using a named function expression:

var obj = {
    fn: function namedFunction () { }
};

This works in all browsers, but IE 8 and lower don't adhere to the specification which says the function is only available by this name inside its own block.

Andy E
  • 338,112
  • 86
  • 474
  • 445
  • I thought `jack = function () {}` was the same as `function jack() {}`, or maybe I'm getting mixed up with Scheme... – Skilldrick Dec 17 '09 at 11:19
  • `function jack() {}` is a named function, `jack = function () {}` is an unnamed function assigned to a variable. – Andy E Dec 17 '09 at 11:21
  • it differs: http://stackoverflow.com/questions/336859/javascript-var-functionname-function-vs-function-functionname – gpilotino Dec 17 '09 at 11:23
  • Yes, I've just checked and you're right. As far as I'm concerned, this is the correct answer, as long as functions being passed in are named functions. +1 – Skilldrick Dec 17 '09 at 11:25
  • @gpilotino: what do you mean? – Skilldrick Dec 17 '09 at 11:28
  • @Andy E - +1 for the nice through answer, I reckon thats about as good as it will get 'out of the box' – Lewis Dec 17 '09 at 11:55
  • @Lewis, thanks. I just can't understand why I keep getting voted down! – Andy E Dec 17 '09 at 11:59
  • Thanks all for an extremely fast and good answer. Just change the reqexp to rExp = /^function\s([^\s]+)\s?\\(([a-zA-Z0-9,\s]+)?\\)/; Since it didn't work properly with whitespaces and arguments. I'll post the my solution below. – fredrik Dec 17 '09 at 12:43