views:

352

answers:

1

Let's say I get an anonymous function an need to act on its context, but it's different whether it's binded to "window" or an unknown object.

How do I get a reference to the object an anonymous function is called from?

EDIT, some code :

var ObjectFromOtherLibIAmNotSupposedToknowAbout = {
    foo : function() {
        // do something on "this"
    }
}

var function bar(callback) {
     // here I want to get a reference to 
     // ObjectFromOtherLibIAmNotSupposedToknowAbout
     // if ObjectFromOtherLibIAmNotSupposedToknowAbout.foo is passed 
     // as callback
}

bar(ObjectFromOtherLibIAmNotSupposedToknowAbout.foo);

You might legitimately ask, why does the heck would you like to do something like that. Well, I first wanted to unpack arguments passed as an array. Just like the Python "*" operator does :

>>> args = [1,2,3]
>>> def foo(a,b,c) :
        print a,b,c
>>> foo(*args)
1 2 3

I dug in SO and found a post telling to use "apply()" :

function bar(callback, args){
    this[callback].apply(this, args);
}

Interesting since it's going to use the current "this" if in an object, and "window" if not.

But I think there is a problem :

if "bar()" is itself in an object, then "this" will refer to the "bar()" container, therefor it won't worK.

BTW, I would like not to pass the scope as a parameter.

I can of course concatenate the arguments and the function as a string then use eval, but I'd like to use this only if I can't find something cleaner.

Of course, if it's just impossible (after all, it could be), then I'll do :

function foo(func, args) 
{
    eval("func("+args.join(", ")+");");
}

EDIT 2 : full scenario, as asked in comments.

I'm using qunit to perform unit tests in Javascript. It's cool, but I miss a way to check if something raises an Exception.

The most basic test is done that way :

/**
 * Asserts true.
 * @example ok( $("a").size() > 5, "There must be at least 5 anchors" );
 */
function ok(a, msg) {
    _config.Test.push( [ !!a, msg ] );
}

The idea is to make something like :

jqUnit.prototype.error = function(func, args, msg) 
{
    try 
    {
        eval("func("+args.join(", ")+");");
        config.Test.push( [ false, msg + " expected : this call should have raised an Exception" ] );
    } catch(ex)
    {
        _config.Test.push( [ true, msg ] );
    }
};

If I could get rid of eval, it would be great. And why don't I want to use the scope as a parameter ? Because you may want to loop on a container referencing 20 functions with different scopes and test them all in the loop instead of writting the stuff by hand.

+3  A: 

e-satis,

The only way to do is to use call or apply method to set correct 'context'.

To solve your problem, modify your bar function to accept callback function as well as the scope to apply to that callback function.

function bar(callback, scope)
{
    callback.apply(scope);
}

bar(ObjectFromOtherLibIAmNotSupposedToknowAbout.foo, ObjectFromOtherLibIAmNotSupposedToknowAbout);

Alternatively, you can use a 'bind' method.

Function.prototype.bind = function(context) {
  var fun = this;
  return function(){
    return fun.apply(context, arguments);
  };
};

Now, you can leave your bar function untouched and modify the calling code to look like,

bar(ObjectFromOtherLibIAmNotSupposedToknowAbout.foo.bind(ObjectFromOtherLibIAmNotSupposedToknowAbout));

EDIT:

As I noted in the comment, it's responsibility of the calling code to pass a correct callback function. Your 'bar' function can not determine what scope to use, period.

Take this for an example,

var LibObject = { foo: function() { //use this inside method } };

var fooFunction = LibOjbect.foo;
bar(fooFunction);

How are you going to figure out what should be the scope? There is nothing for you to 'parse' now and there is no way you can modify your 'bar' function to make this work.

SolutionYogi
Hello, thanks, but it's for hacking a little extension for an unit test framework, I don't have the scope of the tested function at hand (and shouldn't have).
e-satis
Are you in charge of writing that bar method call or not? In JS, functions are not attached to objects and JS engine will decide what context to use unless you specify it explicitly using call/apply.
SolutionYogi
I am, but I can't ask for 1000 tests to be written in a way (qunit syntax), a certain signature, and make on exception for a little hack I coded myself for convenience. It would break the API consistency, it not worth it. No big deal, this question is not a matter of life or death :-)
e-satis
You can always make the 'scope' parameter optional. You use the parameter if it's supplied, otherwise you don't. To look at it another way, it's actually responsibility of the calling code to pass a correct callback function. They should be using the 'bind' method as I outlined above. Alternatively, you can help your calling code by taking in the optional 'scope' argument. I hope I am making sense.
SolutionYogi
You are but it's not about setting the parameter or not. If people use this function, they will want to set the scope. It will just feel unatural. It's not about doing the thing. I can do it, using your technic or eval. I just wanted to know if there was a elegant way to do so. As I said, if not, well, nobody cris :-) Thank you for your answer anyway.
e-satis
Please leave the scope burden to the calling code, they can always use the 'bind' method or something similar. Also, please do not use EVAL in your JS code.
SolutionYogi
Lol, don't worry, it's in the testing code, no injection possible. If jQuery uses eval in it's core, if think using it on the server side for a unit test is no too dangerous. But you are right, I'd prefer not to.
e-satis