Change variable scope of function in Javascript

Go To StackoverFlow.com

2

I'm not interested in call or apply to change the this reference. Just for my own interest, I'm playing with an idea for another require technique for javascript that would make for some cleaner definitions, and the goal was to not have to pass arrays or doubly reference module names for my definitions.

I have a sample solution (just a proof of concept) using toString and eval on a function but I'm wondering if there is a safer or more efficient way to do this.

// Sample module libraries (would probably be in their own files)
someModules = { 
    testModule: {test: function(){console.log("test from someModule")}},
    anotherModule: { doStuff: function(){console.log("Doin stuffs!");}}
};

sampleRequire = function() {

    // Load the modules
    for (var i=arguments.length-2; i>=0; --i){

        // Create a local variable reference to the module
        eval ('var '+arguments[i]+' = someModules.'+arguments[i].toString());
    }

    // Redefine the programmer's function so that it has my local vars in its scope
    eval("var fn = "+arguments[arguments.length-1]);

    return fn;
}

// Main code...
sampleRequire( 'testModule', 'anotherModule',
    function(){ 
        testModule.test();
        anotherModule.doStuff();
    }
)();

Edit:

Pointy made an excellent point that this would completely destroy the main function's scope, which would often times be unacceptable. Ideally I'd like to see the module variables being added to the function's scope without clobbering its other scoped variables (with the exception of the module names--the programmer must know not to use the same name for two things). I'm betting this is probably impossible, but I would still love to see some ideas.

Another goal is to do this flexibly without having to add arguments per module to the main function. Otherwise we're back to square one with CommonJS styles (which I'm not trying to fight, just curious about scope!).

2012-04-05 15:21
by andyortlieb
Well if you not interested in the call or apply and prefer to use nasty eval is your choice it will work but you'll never get to production with that and if you do then maybe you deserve a spanking. edit: just joke don't take it badly. But after many years of development I often says "It's not because we can that we should do it - elmuchacho 2012-04-05 15:26
If the code in "someModules" was itself written to have other local variables in closure scope, your approach will break them completely - Pointy 2012-04-05 15:29
No offense taken! This whole question is really for amusement and experimenting. But apply and call do not solve the problem in question - andyortlieb 2012-04-05 15:29
Pointy, very good point - andyortlieb 2012-04-05 15:31
I think the only way to do this without using eval and without rewriting the callback function (e.g. by adding arguments) is by storing the modules into the global scope, or making sure that the callback is defined within the same scope that defines the modules - Benjie 2012-04-05 15:42
Consider using a function that takes two arguments (a function and an array) to avoid the need for arguments - user123444555621 2012-04-06 08:47


1

I tend to say "you're doing it wrong". Using undeclared variables is never a good idea, even though you can.

Here's another hack which writes the modules to the global object. However, this may have side effects on the methods called from the main function.

sampleRequire = function() {

    var cache = {};
    var moduleNames = [].slice.call(arguments);
    var fn = moduleNames.pop();

    return function () {
        var result, name, i;
        // export modules to global object
        for (i = 0; i < moduleNames.length; i++) {
            name = moduleNames[i];
            cache[name] = window[name]; // remember old values
            window[name] = someModules[name];
        }
        result = fn.apply(null, arguments);
        // restore original global stuff
        for (i = 0; i < moduleNames.length; i++) {
            name = moduleNames[i];
            window[name] = cache[name];
        }
        return result;
    };
}

I also tried some magic with the with keyword, which was basically made for precisely what you want. However, it looks like it doesn't work without eval in this case.

2012-04-06 08:46
by user123444555621


0

I can't think of any other way of doing what you're after. I also suspect this may be one of the few use cases of eval that is not evil. But do keep in mind that the modules could rely on their scopes and this could break them.

2012-04-05 15:30
by Hubro
Oh, I still think it's evil - andyortlieb 2012-04-05 15:45


0

How about something like:

someModules = { 
  testModule: {test: function(){console.log("test from someModule")}},
  anotherModule: { doStuff: function(){console.log("Doin stuffs!");}}
};

function requireModules() {
  var all = [];
  for (var i = 0, l = arguments.length; i<l; i++) {
    all.push(someModules[i]);
  }
  return all;
}

(function(testModule,anotherModule){
  testModule.test();
  anotherModule.doStuff();
}).apply(null,requireModules('testModule','anotherModule'));
2012-04-05 15:32
by Benjie
window is only the global object in browsers. In Node.js, for example, the global object is global. In Web workers, the global object is self - Rob W 2012-04-05 15:33
@RobW, you're quite right - I meant to pass this - Benjie 2012-04-05 15:39
I agree with the approach. However, read the first line at the question ; - Rob W 2012-04-05 15:46
I did: I'm not interested in call or apply to change the this reference. I didn't change the this reference - in fact I maintained it. I was using apply to call the function with a dynamic number of arguments - Benjie 2012-04-06 08:46
By default, this in (function(){})() points to the global object, or undefined in strict mode. By using apply, you explicitly set the this inside the function to the this outside the function, which might be the global object. So, you did update the this reference in the function - Rob W 2012-04-06 08:50
True, though my point is that that was not the reason for doing it - merely to call it with a dynamic number of arguments - Benjie 2012-04-06 09:09
Ads