Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get the "this" (scope) of a Javascript anonymous function?

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.

like image 565
e-satis Avatar asked Oct 11 '09 14:10

e-satis


People also ask

How do you access anonymous functions?

An anonymous function is not accessible after its initial creation, it can only be accessed by a variable it is stored in as a function as a value. An anonymous function can also have multiple arguments, but only one expression.

Can anonymous function return JavaScript?

They are always invoked (called) using the variable name. Also, we create anonymous functions in JavaScript, where we want to use functions as values. In other words, we can store the value returned by an anonymous function in a variable.

What is this in anonymous function?

An anonymous function is a function that is not stored in a program file, but is associated with a variable whose data type is function_handle . Anonymous functions can accept multiple inputs and return one output. They can contain only a single executable statement.

What is JavaScript function scope?

JavaScript has function scope: Each function creates a new scope. Variables defined inside a function are not accessible (visible) from outside the function. Variables declared with var , let and const are quite similar when declared inside a function. They all have Function Scope: function myFunction() {


1 Answers

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.

like image 123
SolutionYogi Avatar answered Sep 20 '22 12:09

SolutionYogi