-3

I have this static factory function which can create a new instance of a class:

  static create(opts, fn, cb) {
    return new Bar(opts, fn, cb);
  }

that works fine. Say we have a method alias like so:

  foo(a, b, c) {
    this.whatever(b,c) // whatevs
  }

  fooAlias(){
   return this.foo.apply(this, arguments);
  }

so my question is, using JS, is there a way to create a generic function that can create a new instance of a class and handle dynamic arguments?

something like:

  static create() {
    return new Bar(...arguments);
  }

is that correct? Is that the only way?

TypeScript transpiles this:

class Bar {

}

const makeBar = function () {
 return new Bar(...arguments);
};

to this for example:

var Bar = (function () {
    function Bar() {
    }
    return Bar;
}());
var makeBar = function () {
    return new (Bar.bind.apply(Bar, [void 0].concat(arguments)))();
};
Alexander Mills
  • 90,741
  • 139
  • 482
  • 817
  • in other words, without the spread operator, I am not sure how you would do this with ES5 – Alexander Mills Dec 26 '17 at 20:59
  • 1
    I have never seen `Function.prototype.bind` called with `Function.prototype.apply`, until now lol – Alexander Mills Dec 26 '17 at 21:02
  • I don't see the problem here. Seems like the solution is to use the spread syntax and a transpiler. Right? – Patrick Roberts Dec 26 '17 at 21:21
  • Your question has already an answer [here](https://stackoverflow.com/questions/1606797/use-of-apply-with-new-operator-is-this-possible). – Angel Politis Dec 26 '17 at 21:45
  • @PatrickRoberts right, but what would you have done in 2012 is the question. The transpiler gives us an answer, but that's pretty complicated. The purpose of the question was if there is a simpler way to do it, not sure. – Alexander Mills Dec 26 '17 at 21:52

2 Answers2

1

Since function overloading does not exists in javascript, you're only bet is using arguments.

If you know the number of parameters that would be passed, you could make those parameters optional and give them default values.

 static create(opts = {}, fn = () => {}, cb = null, ...) {
    return new Bar(opts, fn, cb);
  }

if the default parameter route does not fit you, then your best bet is arguments.

p.s.

To add my two cents, I don't know what your problem is, but it seems that you're already pass the point of K.I.S.S. Maybe you should to rethink your code, as this solution is hardly maintainable.

Stralos
  • 4,895
  • 4
  • 22
  • 40
1

Because of the difficulty in calling a constructor with an arbitrary list of arguments in ES5, I've seen some code that does something like this. It's not elegant at all, but it does get the job done as long as there's a limit to how many arguments you need to support:

function createBar() {
    if (arguments.length === 0) {
        return new Bar();
    } else if (arguments.length === 1) {
        return new Bar(arguments[0]);
    } else if (arguments.length === 2) {
        return new Bar(arguments[0], arguments[1]);
    } else if (arguments.length === 3) {
        return new Bar(arguments[0], arguments[1], arguments[2]);
    } else if (arguments.length === 4) {
        return new Bar(arguments[0], arguments[1], arguments[2], arguments[3]);
    } else {
        throw new Error("too many arguments for Bar()");
    }
}

You could even make a generic version of this that you pass in the constructor for so you can reuse the if/else code for any constructor:

// generic way of calling constructor with variable args
function createFn(fn /* other args here */) {
    if (arguments.length === 1) {
        return new fn();
    } else if (arguments.length === 2) {
        return new fn(arguments[1]);
    } else if (arguments.length === 3) {
        return new fn(arguments[1], arguments[2]);
    } else if (arguments.length === 4) {
        return new fn(arguments[1], arguments[2], arguments[3]);
    } else if (arguments.length === 5) {
        return new fn(arguments[1], arguments[2], arguments[3], arguments[4]);
    } else {
        throw new Error("too many arguments for Bar()");
    }
}

function createBar() {
   var args = Array.prototype.slice.call(arguments);
   args.unshift(Bar);
   return createFn.apply(null, args);
}

In cases like this where you control all the code, it's often just better to redesign the constructor to accept a single object with optional properties and then it's trivial to solve this problem in ES5 as you just pass the single object through and the constructor looks for what properties are on the object.

Here's another scheme that I just came across: Use of .apply() with 'new' operator. Is this possible? which is similar to what you saw with .bind() in TypeScript.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • 1
    Minor nitpick, every `else` here is redundant. – Patrick Roberts Dec 27 '17 at 00:44
  • @PatrickRoberts - I follow your logic. But, I'm pretty sure the compiler can branch more effectively this way and I think it's just more declarative this way as you're showing anybody reading this code that the code will execute one and only one of these statements. Yes, that happens to be true even without the `else` because each contains a `return`, but this way is more declarative IMO. – jfriend00 Dec 27 '17 at 02:21
  • You know what else is redundant? every downvote on the OP – Alexander Mills Jan 03 '18 at 04:52
  • In this case, I'd love to read where if/elseif/else is easier on the compiler than if/return if/return – Alexander Mills Jan 03 '18 at 04:53
  • @AlexanderMills - I'm not going to debate compiler efficiency. If I could edit that out of my comment I would. I personally think the code is easier to read by using `else` because each of these if statements really are `if/else` even if coded as `if/return` and thus I find it more declarative to use `if/else` (meaning more obvious when reading the code) the so that's the style I produce. That's a personal choice. If someone else has a different opinion, so be it for the code they produce. – jfriend00 Jan 03 '18 at 05:00