Reputation: 4776
I have a constructor and I don't know the number of arguments it needs, for instance:
function someCtor(a,b,c){
var that = this;
that.a = a;
that.b = b;
that.c = c;
}
I need to create a function which will return the instance of that constructor with a dynamic amount of arguments:
function makeNew(ctor, arguments){
// this doesn't work, but it shows what I'm trying to achieve
return new ctor.apply(arguments);
}
I want to use the function to pass the dynamic arguments to the constructor like below:
var instanceOfCtor = makeNew(someCtor, [5,6,7]);
How to implement this function?
Upvotes: 1
Views: 231
Reputation: 1073978
Note: See the ES2015 compatibility note at the end.
You do it by first creating an object setting its underlying prototype to the object the prototype
property on the constructor refers to via Object.create
, then calling the constructor via Function#apply
:
function makeNew(ctor, arguments){
var obj = Object.create(ctor.prototype);
var rv = ctor.apply(obj, arguments);
return rv && typeof rv === "object" ? rv : obj;
}
Note the bit of fiddling at the end, so we're emulating the new
operator correctly: When you call a constructor via new
, if it returns a non-null
object reference, that ends up being the result of the new
expression; if it returns anything else (or nothing), the object created by new
is the result. So we emulate that.
Even on pre-ES5 browsers, you can emulate enough of Object.create
to do that:
if (!Object.create) {
Object.create = function(proto, props) {
if (typeof props !== "undefined") {
throw new Error("The second argument of Object.create cannot be shimmed.");
}
function ctor() { }
ctor.prototype = proto;
return new ctor;
};
}
If the constructor you're calling was created via ES2015's class
syntax, the above won't work, because you can't call ES2015 class
constructors that way. For example:
class Example {
constructor(a, b) {
this.a = a;
this.b = b;
}
}
const e = Object.create(Example.prototype);
Example.apply(e, [1, 2]); // TypeError: Class constructor Example cannot be invoked without 'new' (or similar)
The good news is that will only happen on an ES2015+-compatible JavaScript engine, and only if the constructor was created via class
; the bad news is that it can happen.
So how do you make your makeNew
bulletproof?
It turns out this is quite easy, because ES2015 also added Reflect.construct
, which does exactly what you want makeNew
to do but does it in a way that's compatible with both class
constructors and function
constructors. So you can feature-detect Reflect.construct
and use it if it's present (ES2015 JavaScript engine, so a constructor might have been created with class
) and fall back to the above if it's not there (pre-ES2015 engine, there won't be any class
constructors around):
var makeNew; // `var` because we have to avoid any ES2015+ syntax
if (typeof Reflect === "object" && Reflect && typeof Reflect.construct === "function") {
// This is an ES2015-compatible JavaScript engine, use `Reflect.construct`
makeNew = Reflect.construct;
} else {
makeNew = function makeNew(ctor, arguments){
var obj = Object.create(ctor.prototype);
var rv = ctor.apply(obj, arguments);
return rv && typeof rv === "object" ? rv : obj;
};
}
That's pure ES5 syntax, so runs on ES5 engines, but uses ES2015's Reflect.construct
if it's present.
Upvotes: 5