Change variable scope of function in Javascript

2019-07-12 13:11发布

问题:

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!).

回答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.



回答2:

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.



回答3:

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'));