3

I'm currently needing to pass a class through a variable to a function which dynamically calls their constructor and so on. Short example:

class MyClass {
  constructor ($q) {
    console.log($q);
  }

  expose () {
    return {};
  }
}

const myclass = function () {
  // My concern is this ------.
  // I need to pass it        |
  // as a variable           \|/
  //                          '
  let obj = Object.create(MyClass.prototype);
  obj.constructor.apply(obj, arguments);
  return {}; // other stuff goes here
};
myclass.$inject = ['$q'];
export {myclass};

My idea is to pass MyClass as a variable, to have it this way:

const myclass = function (classVariable) {
  let obj = Object.create(classVariable.prototype);
  obj.constructor.apply(obj, arguments);
  return {}; // other stuff goes here
};

So that I can call it like let newclass = myclass(MyClass);. This way if I have different classes (MyClass2, MyClass3, and so on) I don't have to repeat code in each file. If I try to do it this way though, Chrome throws an error saying that it cannot use a class as variable or something like that.

So, what would be the best approach to do this and avoid repeating code? it's actually a redesign I'm working on to avoid the copy-paste disaster made by someone else I need to fix. Thanks in advance!

DARKGuy
  • 843
  • 1
  • 12
  • 34
  • 4
    **Why** do you need to use `Object.create` rather than just calling the constructor via `new` as it's intended to be used? – T.J. Crowder Aug 25 '16 at 21:29
  • If you're doing all this in order to pass on the `arguments` with `.apply()`, you can use the spread operator instead. –  Aug 25 '16 at 21:32
  • @T.J.Crowder because I can't do new when it's a variable, not a class. I got on the internet and SO and suggestions were to use object.create instead. I'm attempting to do something alike to C# Generics ( types) if you've ever handled them. – DARKGuy Aug 25 '16 at 22:18
  • @squint spread operator? how so? can you shed me some light please? :) – DARKGuy Aug 25 '16 at 22:18
  • 1
    @ DARKGuy: *"because I can't do new when it's a variable, not a class."* Yes, you can. See my answer. The surprising thing about JavaScript, when you're coming to it from Java or C# or C++, is how *simple* it is. A class is just a constructor. A constructor is just a function (okay, that's not quite true, but it's really close). You can refer to a function from a variable or argument (which are basically the same thing). :-) – T.J. Crowder Aug 25 '16 at 22:42
  • In JavaScript functions (which includes constructor functions) are **values**, just like a string or number values. You can pass them around, assign them variables, just like you can with any other values. `new x` only expects that `x` resolves to a (constructor) function. As T.J. said, JavaScript is actually rather simple. – Felix Kling Aug 26 '16 at 00:58

2 Answers2

11

I think your confusion is that you think that class constructors cannot be referenced by variables. They can, they're just functions. So:

class Foo {
    message() {
        console.log("I'm a Foo");
    }
}
class Bar {
    message() {
        console.log("I'm a Bar");
    }
}
function test(C) {
      let obj = new C();
      obj.message(); // "I'm a Foo" or "I'm a Bar", depending on
                     // whether Foo or Bar was passed in
}
test(Foo);
test(Bar);

Your pattern of calling var obj = Object.create(X.prototype) followed by X.apply(obj, /*...args here...*/) would work in ES5 and earlier, but ES2015's classes don't allow it. To construct instances from them, you have to use the new operator. The reason for that has to do with subclassing and setting new.target, so that if the instance has reason to create new instances (as Promise does), it can do that in an unambiguous way.

Which seems like it could be a step back, but if for some reason you have the constructor arguments as an array rather than discrete items, spread notation lets you use new anyway:

let obj = new C(...args);

So if you need a generic function that accepts a class constructor and an array of arguments and needs to return an instance of that class using those arguments, it would look like this:

function createInstanceOf(C, args) {
    return new C(...args);
}
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • I created my Factory this way, and everything is ok, except instanceof ParentClass. Do you know how to fix it? – Crusader May 04 '22 at 20:11
  • @Crusader - Not without code, no. Neither the question nor the answer has any inheritance, and it'll depend on how you set that inheritance up. (And whether you're doing what the OP is doing -- which I don't recommend -- or what I suggest at the end of the answer.) – T.J. Crowder May 05 '22 at 06:03
0

I believe what you are looking for is a closure:

function makeFactory(constructor) {
  return function(...args) {
    let obj = new constructor(...args);
    return {}; // other stuff goes here
  };
}
const myclass = makeFactory(MyClass);
// then use
myClass().expose() // or whatever you were going to do with it
Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375