24

I think the following code will make the question clear.

// My class
var Class = function() { console.log("Constructor"); };
Class.prototype = { method: function() { console.log("Method");} }

// Creating an instance with new
var object1 = new Class();
object1.method();
console.log("New returned", object1);

// How to write a factory which can't use the new keyword?
function factory(clazz) {
    // Assume this function can't see "Class", but only sees its parameter "clazz".
    return clazz.call(); // Calls the constructor, but no new object is created
    return clazz.new();  // Doesn't work because there is new() method
};

var object2 = factory(Class);
object2.method();
console.log("Factory returned", object2);
Damjan Pavlica
  • 31,277
  • 10
  • 71
  • 76
avernet
  • 30,895
  • 44
  • 126
  • 163

8 Answers8

28

A simpler, cleaner way with no "factories"

function Person(name) {
  if (!(this instanceof Person)) return new Person(name);
  this.name = name;
}

var p1 = new Person('Fred');
var p2 = Person('Barney');

p1 instanceof Person  //=> true
p2 instanceof Person  //=> true
Cory Martin
  • 101
  • 3
  • 4
25

Doesn't this work?

function factory(class_, ...arg) {
    return new class_(...arg);
}

I don't understand why you can't use new.

9me
  • 1,078
  • 10
  • 36
dave4420
  • 46,404
  • 6
  • 118
  • 152
  • 2
    the stipulation was no use of "new" – Jimmy Oct 16 '09 at 23:23
  • 1
    Darn yes! This works great. I thought (incorrectly!) that I couldn't use the "new" keyword in this case, but of course it doesn't matter if new takes a "class" that was just defined, or if that "class" is passed in parameter to a function and the "new" is called later. Me stupid. – avernet Oct 16 '09 at 23:43
  • 1
    Doesn't work for me. All I'm getting on FF22 is "TypeError: class_ is not a constructor" – andig Jun 27 '13 at 16:21
  • 2
    okay hot shot, how do you apply an argument list to this newly created class? ;) new class_().apply(this,array) i don't think so – Lpc_dark May 24 '14 at 14:57
  • @Lpc_dark hits the nail on the head! Maybe you could do `function factory(class_, ...args) {return new class_(...args)}` ? – Stijn de Witt Apr 06 '17 at 22:28
7

If you really don't want to use the new keyword, and you don't mind only supporting Firefox, you can set the prototype yourself. There's not really any point to this though, since you can just use Dave Hinton's answer.

// This is essentially what the new keyword does
function factory(clazz) {
    var obj = {};
    obj.__proto__ = clazz.prototype;
    var result = clazz.call(obj);
    return (typeof result !== 'undefined') ? result : obj;
};
Matthew Crumley
  • 101,441
  • 24
  • 103
  • 129
3

I guess browser independent solution would be better

function empty() {}

function factory(clazz /*, some more arguments for constructor */) {
    empty.prototype = clazz.prototype;
    var obj = new empty();
    clazz.apply(obj, Array.prototype.slice.call(arguments, 1));
    return obj;
}
3

Because JavaScript doesn't have classes, let me reword your question: How to create a new object based on an existing object without using the new keyword?

Here is a method that doesn't use "new". It's not strictly a "new instance of" but it's the only way I could think of that doesn't use "new" (and doesn't use any ECMAScript 5 features).

//a very basic version that doesn't use 'new'
function factory(clazz) {
    var o = {};
    for (var prop in clazz) {
        o[prop] = clazz[prop];
    }
    return o;
};

//test
var clazz = { prop1: "hello clazz" };
var testObj1 = factory(clazz);
console.log(testObj1.prop1);    //"hello clazz" 

You could get fancy and set the prototype, but then you get into cross-browser issues and I'm trying to keep this simple. Also you may want to use "hasOwnProperty" to filter which properties you add to the new object.

There are other ways that use "new" but sort of hide it. Here is one that borrows from the Object.create function in JavaScript: The Good Parts by Douglas Crockford:

//Another version the does use 'new' but in a limited sense
function factory(clazz) {
    var F = function() {};
    F.prototype = clazz;
    return new F();
};

//Test
var orig = { prop1: "hello orig" };
var testObj2 = factory(orig);
console.log(testObj2.prop1);  //"hello orig"

EcmaScript 5 has the Object.create method which will do this much better but is only supported in newer browsers (e.g., IE9, FF4), but you can use a polyfill (something that fills in the cracks), such as ES5 Shim, to get an implementation for older browsers. (See John Resig's article on new ES5 features including Object.create).

In ES5 you can do it like this:

//using Object.create - doesn't use "new"
var baseObj = { prop1: "hello base" };
var testObj3 = Object.create(baseObj);
console.log(testObj3.prop1);

I hope that helps

grahamesd
  • 4,773
  • 1
  • 27
  • 27
2

Another way:

var factory = function(clazz /*, arguments*/) {
    var args = [].slice.call(arguments, 1);
    return new function() { 
        clazz.apply(this, args)
    }
}
lun
  • 1
  • 1
0

To answer the question more literally, i.e. how to have myClass() return new myClass()... It's not possible, and here's why...


You'd have to do it like this, to make sure that the class name exists and that you're capturing calls to myClass() (using apply functionality, from Proxy/trap/handler-land):

class A {

}
A.prototype.apply = function() {
  return new A();
}
A(); //Error occurs here.

OR:

class B {

}
B.apply = function() {
  return new B();
}
B(); //Error occurs here.

And the reason this doesn't work is the output when you try to evaluate either of the above: Uncaught TypeError: class constructors must be invoked with 'new'


Thus, JavaScript literally does not allow it, because you have declared it to be a class type. However, you can of course have a separately named function that creates a new instance for you, like in the answer above, or more simply:

class A {

}
//Note: a != A.
function a() {
  return new A();
}

OR, another way to approach this problem is to not use a class, per-se, but a function or a regular JS object {}, like in the older days of JavaScript. Multiple other answers show how to do this.

Andrew
  • 5,839
  • 1
  • 51
  • 72
-1

What you could also do is use eval.

Of course there are security concerns with eval, but is it really different to any other dynamic instanciation?

await import("/path/to/module") //use this to dynamically load module if you like
let obj = `eval new ${classname}();`
Martin Meeser
  • 2,784
  • 2
  • 28
  • 41