5

Possible Duplicate:
Use of .apply() with 'new' operator. Is this possible?

I have 5 or 6 variable assignments of the form

var analyteSelection = new TemplatedSelectionContainer($('.analyte-container', this), helpers, optionsTemplate);
var instrumentSelection = new AssetBackedSelection($('.instrument-container', this), helpers, optionsTemplate, Assets.instruments, 'Instrument');
var methodSelection = new AssetBackedSelection($('.method-container', this), helpers, optionsTemplate, Assets.methods, 'Method');

As you can see, a significant amount of portion of these constructors are very much alike. It would be nice if I could create a little generic currying builder that would allow me to do something like:

var newSel = selectionContainerBuilder(this, helpers, optionsTemplate)
var analyteSelection = newSel(TemplatedSelectionContainer, '.analyte-container');
var instrumentSelection = newSel(AssetBackedSelection, '.instrument-container', Assets.instruments, 'Instrument');
var methodSelection = newSel(AssetBackedSelection, '.method-container', Assets.methods, 'Method');

I can achieve something similar with

var selectionContainerBuilder = function(ctx, helpers, optionsTemplate) {
  return function(FuncDef, selector, a, b, c, d, e, f) {
    return new FuncDef($(selector, ctx), helpers, optionsTemplate, a,b,c,d,e,f);
  }
}

But seriously ick. I would like to just be able to splice the first three known parameters to the beginning of the arguments array and apply it to the FuncDef but I'm being foiled by the need to use the new operator.

And before someone asks, I can't do new-operator enforcement inside of FuncDef because it's being generated by the coffeescript class keyword.

Community
  • 1
  • 1
George Mauer
  • 117,483
  • 131
  • 382
  • 612

2 Answers2

3

Of course it can be done. This is the one case where eval turns out to be useful.

function newApply(cls, args) {
    var argsAsString = [];
    for (var i = 0, l = args.length; i < l; i++) {
        argsAsString.push('args[' + i + ']');
    }
    return eval('new cls(' + argsAsString.join(',') + ')');
}

(stolen from another thread)

Community
  • 1
  • 1
user123444555621
  • 148,182
  • 27
  • 114
  • 126
  • Oh that's...a good point actually. Hmm, wonder if this has any scoping issues? – George Mauer Oct 06 '11 at 22:34
  • Scoping should not be a problem, since `cls` and `args` take their scope chains with them as they're passed to `newApply`. – user123444555621 Oct 07 '11 at 06:24
  • I wonder why (a) you choose this answer and not the accepted one from the other question and (b), you didn't vote to close this as a duplicate? – Felix Kling Oct 07 '11 at 06:24
  • @FelixKling Because while I had accepted the other answer initially clearly "it can be done" supercedes "it can't be done". Both answers are excellent and worth an up-vote. It is not a duplicate because it is asking a subtly different thing which is why it got different answers. – George Mauer Oct 07 '11 at 14:18
2

Congratulations! You have just found one of the ugliest secret warts in Javascript and what you want to do can't be done1.

The common workarounds are either creating wrapper functions (like you did) or restructuring the constructor functions to receive lists/objects 2 instead of lots of arguments.


1 There is a proposal for a "..." syntax for multiple arguments in Harmony (the next version of JS) but untill then there is no way to pass a variable number of arguments to a constructor.

2 Passing a list or object is usually a good idea when you have tons of arguments. It protects you from jumbling the order by accident and makes it way easier to handle optional parameters (like it seems to be in your case).

new Foo("arg1", {
    helpers: helpers,
    options: optionsTemplate,
    intruments:  Assets.instruments
});

Edit: Just found another question on this same topic: Use of .apply() with 'new' operator. Is this possible?

Community
  • 1
  • 1
hugomg
  • 68,213
  • 24
  • 160
  • 246