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:
- Creating a new object instance (you're doing that).
- Setting that object's internal prototype to constructor function's
prototype
property.
- Setting that object's
constructor
property.
- Calling the constructor function with that object instance as the
this
value (you're doing that).
- Handling the special return value from the constructor function.
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.