0

I want to pass a varying number of argument to varying functions. I set up this basic test:

function overview(arg1, arg2, arg3) {
    console.info('arg1 is ' + arg1);
    console.info('arg2 is ' + arg2);
    console.info('arg3 is ' + arg3);
}

function modules(method, args) {
    this[method].apply(null, args);
}

modules('overview', new Array('test1', 'test2'));​

So, I use 'apply' to pass an array with arguments via the 'modules' function to the 'overview' function. This works fine, except for the this[method] part. I read about this in this Q&A: Calling dynamic function with dynamic parameters in Javascript and it seems marvelous. However, I keep getting 'TypeError' errors and I can't figure out how to resolve this.

Of course, I could use a switch within the modules function to call the correct method, but that is unnecessary bulk (hopefully!). I've made a JSFiddle to 'fiddle' with: http://jsfiddle.net/QFpRc/. Hope anyone can solve and/or explain this.

Community
  • 1
  • 1
kasimir
  • 1,506
  • 1
  • 20
  • 26

3 Answers3

1

this does not make sense in your context.

You have to define the functions in the global scope, or on another namespace, and use window, or the name of that namespace instead of this.

// Globally:    
function modules(method, args) {
    window[method].apply(null, args);
}
modules('overview', ['test1', 'test2']);​

// Locally:
var namespace = {};
namespace.overview = overview; // Defined previously
function modules(method, args) {
    namespace[method].apply(null, args);
}
modules('overview', ['test1', 'test2']);​

If you want to use this method locally, without predefining a namespace, there is no other option than using eval:

// Locally
function modules(method, args) {
    eval(method).apply(null, args);
}
modules('overview', ['test1', 'test2']);​

Expanding the implementation:

If you want to handle unknown functions without throwing errors, use:

function modules(method, args) {
    var ns = window; // or namespace (method 2)    or eval(method)  (method 3)
    if (ns.hasOwnProperty(method) && typeof ns.method === 'function') {
        ns.apply(null, args);
    } else {
        console.log(' Unknown method: ' + method);
    }
}

Demos

  1. Global: http://jsfiddle.net/QFpRc/4/
  2. Local + namespace: http://jsfiddle.net/QFpRc/3/
  3. Local + eval: http://jsfiddle.net/QFpRc/2/
Rob W
  • 341,306
  • 83
  • 791
  • 678
  • Thanks for your quick response. When I use `window` instead of `this`, the result is the same. Also usage of `this` was suggested in the SO question I mentioned, and the rest of the code looks roughly the same. So why can 'they' use it and I not? The namespace and eval solutions both work great, though eval looks way simpler. But can't eval do evil here? – kasimir Feb 23 '12 at 14:34
  • @kasimir Have a look at the demos at the bottom of my answer. Your code does **not** work, because you have selected the `"onload"` option in the checkbox. This **wraps your code in `window.onload = function() { ..your code.. }`**, causing your code to be **local instead of global**. – Rob W Feb 23 '12 at 14:35
  • So it does. Wasn't aware of that, the whole namespace thing still seems tricky to me now and then. Thanks for elaborating. – kasimir Feb 23 '12 at 14:43
  • Hm... I've implemented this like in demo 1, but I've discovered that this doesn't work in IE 8... Any ideas on a solution? – kasimir Mar 01 '12 at 13:32
  • @kasimir I have successfully tested demo 1 (see http://jsfiddle.net/QFpRc/5/) in IE8. Are you sure that you've implemented it correctly? (**[screenshot of demo 1 in IE8](http://i.stack.imgur.com/TrJmA.png)**). – Rob W Mar 01 '12 at 14:05
  • That (5) works. And when I ran 4 again, it worked, which puzzled me. But actually, it probably was my own fault, because I was watching a console from a different IE and so I got something like `console undefined`. Anyway, my issue seemed to be that sometimes there are no arguments, which IE doesn't like. Like so: http://jsfiddle.net/QFpRc/6. I solved it like this: http://jsfiddle.net/QFpRc/7 – kasimir Mar 01 '12 at 14:30
  • @kasimir `window[method].apply(null, args || []);` (added `|| []`) will solve the issue. – Rob W Mar 01 '12 at 14:32
1

Your reference to this in the modules function declaration points to the modules objects. Since modules doesn't have an 'overview' property, there is no 'overview' method to invoke. As @Rob W stated, you could explicitly reference the global window object, where your functions are defined, or you could define your own object:

var myObj = {
    overview: function(arg1, arg2, arg3) {
        console.info('arg1 is ' + arg1);
        console.info('arg2 is ' + arg2);
        console.info('arg3 is ' + arg3);
    },

    modules: function(method, args) {
        this[method].apply(null, args);
    }
};

myObj.modules('overview', new Array('test1', 'test2'));

See http://jsfiddle.net/QFpRc/1/ for a live example.

James Sumners
  • 14,485
  • 10
  • 59
  • 77
  • Thanks, this gave me some interesting ideas for refactoring my code someday. But for now, Rob W's solution is more straightforward. – kasimir Feb 23 '12 at 14:45
0

Your code will perform faster if you used Function.prototype.bind instead of passing parameters thru a proxy function. Bind will save processing time and memory by applying the context and routing arguments before the actual function call

function overview(arg1,arg2,arg3){
  console.info('arg1 is ' + arg1);
  console.info('arg2 is ' + arg2);
  console.info('arg3 is ' + (arg3 || this.foo));
}
var context = { foo:'bar' };
var contextOverview = overview.bind(context);
var modOverview = function(){}.apply.bind(overview.bind(context), context);

modOerview(['test1','test1'])

A closure'd variant might look something like:

var importModule = (function(){
  var mods = {
    overview:function(arg1,arg2,arg3){
      console.info('arg1 is ' + arg1);
      console.info('arg2 is ' + arg2);
      console.info('arg3 is ' + (arg3 || this.foo()));
      console.warn('This context is ' + this);
    }
  };
  return function exportMod(name, context){
    var mod = mods[name].bind(context);
    return mod.apply.bind(mod,null);
  }
})();

var myContext = (function(){
  var foo = 'bar';
  var overview;
  var context = {
    overview:function(){ return overview() },
    foo:function(){ return foo },
    toString:function(){ return 'my context!' }
  };
  overview = importModule('overview', context);
  return context;
})();

myContext.overview(['test1','test2']);

That being said, why don't you just call the function like normal? I mean, if you know the context and the function name.... .. .

Julian
  • 500
  • 2
  • 10