When I want to call a function in javascript with arguments supplied from elsewhere I can use the apply
method of the function like:
array = ["arg1", 5, "arg3"]
...
someFunc.apply(null, array);
but what if I need to call a constructor in a similar fashion? This does not seem to work:
array = ["arg1", 5, "arg3"]
...
someConstructor.apply({}, array);
at least not as I am attempting:
template = ['string1', string2, 'etc'];
var resultTpl = Ext.XTemplate.apply({}, template);
this does not work wither:
Ext.XTemplate.prototype.constructor.apply({}, template);
Any way to make that one work? (In this particular case I found that new Ext.XTemplate(template)
will work, but I am interested in the general case)
similar question but specific to built-in types and without an answer I can use: Instantiating a JavaScript object by calling prototype.constructor.apply
Thank you.
Time has passed and ES6 and transpilers are now a thing.
In ES6 it is trivial to do what I wanted: new someConstructor(...array)
.
Babel will turn that into ES5 new (Function.prototype.bind.apply(someConstructor, [null].concat(array)))();
which is explained in How to construct JavaScript object (using 'apply')?.
A JavaScript object is syntactically defined as a function, which is itself a first instance and that is cloned to create more instances. In addition, this structure is dynamic, methods (in fact inner functions) and attributes may be added during the execution of the script.
You can create custom dynamic objects by using the classes in the System. Dynamic namespace. For example, you can create an ExpandoObject and specify the members of that object at run time. You can also create your own type that inherits the DynamicObject class.
The Object constructor turns the input into an object. Its behavior depends on the input's type. If the value is null or undefined , it will create and return an empty object. Otherwise, it will return an object of a Type that corresponds to the given value. If the value is an object already, it will return the value.
To create a dynamic object in a loop with JavaScript, we can use the square bracket notation to add properties to an object dynamically. We create an empty object and assign it to the objects variable. Then we add a for loop that loops through some numbers. And we use the numbers as property names with objects[x] .
There's no simple, straightforward way to do this with a constructor function. This is because special things happen when you use the new
keyword to call a constructor function, and so if you're not going to do that, you have to emulate all of those special things. They are:
prototype
property.constructor
property.this
value (you're doing that).I think that's about it, but worth double-checking in the spec.
So if you can avoid it and just use the constructor function directly, I'd do that. :-) If you can't, though, you can still do it, it's just awkward and involves workarounds. (See also this related answer here on StackOverflow, although I cover all of the ground here [and then some] as well.)
Your biggest issue is #2 above: Setting the internal prototype of the object. For a long time, there was no standard way to do this. Some browsers supported a __proto__
property that did it, so you can use that if it's there. The good news is that ECMAScript 5 introduces a way to do this explicitly: Object.create
. So cutting-edge browsers like Chrome will have that. But if you're dealing with a browser that has neither Object.create
nor __proto__
, it gets a bit ugly:
1) Define a custom constructor function.
2) Set its prototype
property to the prototype
property of the real constructor function
3) Use it to create a blank object instance.
That handles the prototype for you. Then you continue with:
4) Replace the constructor
property on that instance with the real constructor function.
5) Call the real constructor function via apply
.
6) If the return value of the real constructor function is an object, use it instead of the one you created; otherwise, use the one you created.
Something like this (live example):
function applyConstruct(ctor, params) {
var obj, newobj;
// Use a fake constructor function with the target constructor's
// `prototype` property to create the object with the right prototype
function fakeCtor() {
}
fakeCtor.prototype = ctor.prototype;
obj = new fakeCtor();
// Set the object's `constructor`
obj.constructor = ctor;
// Call the constructor function
newobj = ctor.apply(obj, params);
// Use the returned object if there is one.
// Note that we handle the funky edge case of the `Function` constructor,
// thanks to Mike's comment below. Double-checked the spec, that should be
// the lot.
if (newobj !== null
&& (typeof newobj === "object" || typeof newobj === "function")
) {
obj = newobj;
}
// Done
return obj;
}
You could take it a step further and only use the fake constructor if necessary, looking to see if Object.create
or __proto__
are supported first, like this (live example):
function applyConstruct(ctor, params) {
var obj, newobj;
// Create the object with the desired prototype
if (typeof Object.create === "function") {
// ECMAScript 5
obj = Object.create(ctor.prototype);
}
else if ({}.__proto__) {
// Non-standard __proto__, supported by some browsers
obj = {};
obj.__proto__ = ctor.prototype;
if (obj.__proto__ !== ctor.prototype) {
// Setting it didn't work
obj = makeObjectWithFakeCtor();
}
}
else {
// Fallback
obj = makeObjectWithFakeCtor();
}
// Set the object's constructor
obj.constructor = ctor;
// Apply the constructor function
newobj = ctor.apply(obj, params);
// If a constructor function returns an object, that
// becomes the return value of `new`, so we handle
// that here.
if (typeof newobj === "object") {
obj = newobj;
}
// Done!
return obj;
// Subroutine for building objects with specific prototypes
function makeObjectWithFakeCtor() {
function fakeCtor() {
}
fakeCtor.prototype = ctor.prototype;
return new fakeCtor();
}
}
On Chrome 6, the above uses Object.create
; on Firefox 3.6 and Opera, it uses __proto__
. On IE8, it uses the fake constructor function.
The above is fairly off-the-cuff, but it mostly handles the issues I'm aware of in this area.
From developer.mozilla: Bound functions are automatically suitable for use with the new operator to construct new instances created by the target function. When a bound function is used to construct a value, the provided this is ignored. However, provided arguments are still prepended to the constructor call.
That said, we still need to use apply to get the arguments out of an array and into the bind call. Further, we need to also reset bind's function as the this argument of the apply function. This gives us a very succinct one-liner that does exactly as needed.
function constructorApply(ctor, args){
return new (ctor.bind.apply(ctor, [null].concat(args)))();
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With