0

Is there a way to change the execution context of a Javascript function manually at execution time?

I am not talking about changing the this reference to point to a different object - I know this can be done with Function.prototype.call()/apply().

I am talking about changing variables accessible to the function, or to be more specific, granting access to variables that are outside of the scope the function is defined in.

Example:

var func;
(function () {
    func = function () {
        alert(test); // no variable called "test" defined, it will
                     // result in a ReferenceError when func() is called
    }
})();


function callFunction (callee) {
    var test ='foobar';
    callee(); // callee does not have access to "test"
}

callFunction(func);

In this example, I'd love the function func to be able to access the variable test defined in callFunciton when it is called. However due to how scope and execution contexts work in Javascript, it doesn't work that way.

I am looking for a way for callFunction to insert a variable called test in the execution context of the callee() call.


In this minimal example, it is of course possible to just add test as a parameter to func().

My use-case however is a framework with a plugin infrastructure, where plugins can specify callbacks and what parameters they take - along with parameter types. The framework then takes care of converting requested data into the requested types and provide them to the callback. This should happen completely transparent to the plugin.

Pseudocode:

Plugin

function callback {
    alert(user.name);
    alert(number);
};
var callbackParams = [
    {name : 'user', type : User},
    {name : 'number', type : Number}
];
defineCallback('event', callback, callbackParams);

Framework

var on = {
    event : [];
}

var User = function (name) {
    this.name = name;
}

function defineCallback (event, callback, params) {
    on[event].push({
        callback : callback;
        params   : params;
    });
}

function handleEvent(event) {
    on[event].forEach(function (handler) {
        for (param of handler.params) {
            // 1) gather data
            // 2) create Object of correct type as specified in 
            //    param.type
            // 3) insert variable called param.name into 
            //    execution context of handler.callback
        }
        handler.callback();
    });
}

So far, I found three solutions to this problem, and I like neither:

  • define parameters in callback, and only define their types in callbackParams, indexed by their order in the function declaration. Disadvantage: parameters have to be declared in two places.
  • use this.paramname instead of just paramname in callback, and use .call()/.apply() in `handleEvent(). Disadvantage: not transparent to plugins.
  • assign the variables to global scope before calling handler.callback() in handleEvent() and delete them again afterwards. Disadvantage: very ugly workaround.

Ideas?

11
  • 1
    Short answer, no, functions don't have access to variables unless they are in scope, so either you pass the variables as arguments or you make them available to the scope of the function, or a higher scope, which should be doable if you don't wrap everything in IIFE's Commented Jun 4, 2016 at 18:48
  • you have to re-eval() the function code to re-assess closures. Commented Jun 4, 2016 at 18:50
  • 1
    My "idea" is that you reconsider your design. It's simply wrong. It's counter intuitive, it's unnatural to the language (and probably to any language), it's untestable (for the plugin developer), and worst of all, the simple solution (explicitly declared arguments) has everything you need without any real downside. Commented Jun 4, 2016 at 19:13
  • 1
    For your use case, you should use function callback(user, number) { … } and just pass the values in the order in which they are given in the callbackParams. You might even drop the name and just specify the type if you don't need them otherwise. Commented Jun 4, 2016 at 19:32
  • 1
    @JohannesH.: You could visually colocate them, e.g. callback.types = [User, Number]; ¶ function callback(user, number) { ¶ … Commented Jun 4, 2016 at 19:39

1 Answer 1

1

You can use a with statement. Be aware it's slow and forbidden in strict mode.

var func;
(function () {
  func = function func() {
    with(func.variables) {
      console.log(test);
    }
  }
})();
function callFunction (callee) {
  callee.variables = {test: "foobar"};
  callee();
  callee.variables = null;
}
callFunction(func);

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for the suggestion! I completely forgot about with as I haven't used it in years for good reason... I like the approach of assigning the variables to a property of the callback function, I didn't think about this one yet. I would have to specify the plugin infrastructure that callbacks have access to their parameters that way then - if a plugin developer uses with in them or not is outside of my responsibility though.
Still, callee.variables is just a rather crude workaround of simply passing the variable object into the function as a parameter. Whether the plugin uses with or not isn't even your decision then.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.