Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Can call/apply be used to invoke constructor?

188 views
Skip to first unread message

David P. Caldwell

unread,
Jun 29, 2009, 4:39:12 PM6/29/09
to
Is there a way to call a function as a constructor with an unknown
number of arguments? It seems like this should be simple, but I can't
find any simple way to do it.

Using the 'arguments' object, for example, it is easy to delegate a
function call to another function:

function myFunc() {
return delegate.apply(this, arguments);
}

But can I delegate a constructor call somehow?

function MyConstructor() {
return new Delegate(...how to pass the arguments?)
}

If I worked hard at it, I feel like I could do this "manually" (with
non-standard extensions) perhaps with something like:

function MyConstructor() {
var object = new Object();
object.__proto__ == Delegate.prototype;
Delegate.apply(object, arguments);
object.constructor = Delegate;
return object;
}

... but I feel like I might be leaving some things out, and it's not
standards-compliant, and it seems like this should be a one-liner. I
just can't figure out how to do it. Am I missing something easy, or an
easy workaround? Or am I missing a good reason there's no way to do this?

-- David P. Caldwell
http://www.inonit.com/

David Mark

unread,
Jun 29, 2009, 4:57:33 PM6/29/09
to
On Jun 29, 4:39 pm, "David P. Caldwell" <ino...@inonit.com> wrote:
> Is there a way to call a function as a constructor with an unknown
> number of arguments?  It seems like this should be simple, but I can't
> find any simple way to do it.
>
> Using the 'arguments' object, for example, it is easy to delegate a
> function call to another function:
>
> function myFunc() {
>       return delegate.apply(this, arguments);
>
> }
>
> But can I delegate a constructor call somehow?
>
> function MyConstructor() {
>       return new Delegate(...how to pass the arguments?)
>
> }

That's a factory, not a constructor.

function MyConstructor() {
Delegate.apply(this, arguments):
}

var myConstructedObject = new MyConstructor();

[snip]

Thomas 'PointedEars' Lahn

unread,
Jun 29, 2009, 5:49:38 PM6/29/09
to
David P. Caldwell wrote:
> Is there a way to call a function as a constructor with an unknown
> number of arguments?

Yes, you can use eval().

> If I worked hard at it, I feel like I could do this "manually" (with
> non-standard extensions) perhaps with something like:
>
> function MyConstructor() {
> var object = new Object();
> object.__proto__ == Delegate.prototype;
> Delegate.apply(object, arguments);
> object.constructor = Delegate;
> return object;
> }
>

> .... but I feel like I might be leaving some things out, and it's not
> standards-compliant,

You don't need to assign to __proto__ for inheritance. Search this group
for clone(), inheritFrom(), and extend().

> and it seems like this should be a one-liner.

It is not a one-liner, but it is quite short. It makes use of the fact
that, contrary to commonly held belief, eval() evaluates its string argument
as a /Program/ in the current execution context:

function construct(Constructor)
{
/*
* or Array.prototype.slice.call(arguments, 1).map(function() { ... })
* in JavaScript 1.6+, compatibles, and with augmented Array.prototype
*/
var args = [];
for (var i = 1, len = arguments.length; i < len; i++)
{
args[i - 1] = "arguments[" + i + "]";
}

/* or args.join(", ") if you need it pretty-printed */
return eval("new Constructor(" + args + ")");
}

function Foo()
{
window.alert(Array.prototype.slice.call(arguments, 0).join(", "));
}

var f = construct(Foo, /bar/g, {baz: 42});

See also the alternate implementations of Function.prototype.apply()
and .call in <http://PointedEars.de/object.js> (to be updated.)


PointedEars

Thomas 'PointedEars' Lahn

unread,
Jun 29, 2009, 5:51:28 PM6/29/09
to

If would have to return a result, then. And s/:/;/

David Mark

unread,
Jun 29, 2009, 6:14:36 PM6/29/09
to
On Jun 29, 5:51 pm, Thomas 'PointedEars' Lahn <PointedE...@web.de>
wrote:

> David Mark wrote:
> > On Jun 29, 4:39 pm, "David P. Caldwell" <ino...@inonit.com> wrote:
> >> But can I delegate a constructor call somehow?
>
> >> function MyConstructor() {
> >>       return new Delegate(...how to pass the arguments?)
>
> >> }
>
> > That's a factory, not a constructor.
>
> > function MyConstructor() {
> >    Delegate.apply(this, arguments):
>
> If would have to return a result, then.  And s/:/;/

Typo on the colon and what would have to return a result?

David Mark

unread,
Jun 29, 2009, 6:18:29 PM6/29/09
to
On Jun 29, 5:49 pm, Thomas 'PointedEars' Lahn <PointedE...@web.de>
wrote:

> David P. Caldwell wrote:
> > Is there a way to call a function as a constructor with an unknown
> > number of arguments?
>
> Yes, you can use eval().

Perhaps you interpreted this question another way. It seemed to me
that the OP wanted to call a "super" constructor.

I'll have to look at your code later, but the use of eval is
troubling.

[snip]

Thomas 'PointedEars' Lahn

unread,
Jun 29, 2009, 6:32:09 PM6/29/09
to
David Mark wrote:

> Thomas 'PointedEars' Lahn wrote:
>> David Mark wrote:
>>> On Jun 29, 4:39 pm, "David P. Caldwell" <ino...@inonit.com> wrote:
>>>> But can I delegate a constructor call somehow?
>>>> function MyConstructor() {
>>>> return new Delegate(...how to pass the arguments?)
>>>> }
>>> That's a factory, not a constructor.
>>> function MyConstructor() {
>>> Delegate.apply(this, arguments):
>> If would have to return a result, then. And s/:/;/
>
> Typo on the colon

And typo on "If" which should have been "It". Figures :)

> and what would have to return a result?

MyConstructor() would need to return a result in order
to be usable as a factory. That's the problem with apply().


PointedEars

Thomas 'PointedEars' Lahn

unread,
Jun 29, 2009, 6:35:09 PM6/29/09
to
David Mark wrote:

> Thomas 'PointedEars' Lahn wrote:
>> David P. Caldwell wrote:
>>> Is there a way to call a function as a constructor with an unknown
>>> number of arguments?
>> Yes, you can use eval().
>
> Perhaps you interpreted this question another way.

Yes, as asked.

> It seemed to me that the OP wanted to call a "super" constructor.

AIUI only his workaround in the attempt for a solution.

> I'll have to look at your code later,

You should.

> but the use of eval is troubling.

It isn't really. But if it troubles you, consider the alternatives.


PointedEars

David Mark

unread,
Jun 29, 2009, 8:56:56 PM6/29/09
to
On Jun 29, 6:32 pm, Thomas 'PointedEars' Lahn <PointedE...@web.de>
wrote:

I wasn't demonstrating a factory. I may have misunderstood the OP's
misunderstanding.

David P. Caldwell

unread,
Jun 29, 2009, 9:45:20 PM6/29/09
to

Maybe I wasn't clear enough -- that doesn't do what I'm looking for,
though. Consider this:

var MyDate = function() {
Date.apply(this, arguments);
}

var mydate = new MyDate(2009,1,1);

alert(mydate);

I want the alert to show February 1, 2009. But instead, it shows
[object Object].

Using "return this;" doesn't improve the situation; same result.

Using "return Date.apply(this, arguments)" is closer -- it calls Date as
a *function* rather than as a constructor. That function returns the
current time as a string, per ECMA-262. So the constructor returns
"this" (constructors return this when you try to return a primitive type
from them) and it shows the same result.

So I'm able to call Date as a *function* using the arguments object
(with apply) but I can't call it as a *constructor* with the arguments
object. So far as I know.

David P. Caldwell

unread,
Jun 29, 2009, 10:25:56 PM6/29/09
to
Thomas 'PointedEars' Lahn wrote:
> David P. Caldwell wrote:
>> Is there a way to call a function as a constructor with an unknown
>> number of arguments?
>
> Yes, you can use eval().
>
>> and it seems like this should be a one-liner.
>
> It is not a one-liner, but it is quite short.

Yes, this approach works! I am still stunned there is no simpler way to
do it (when apply and call are so simple for the non-constructor case).

Here's the code I ended up using as my test case, which uses your
approach, but uses 'this' in the construct function, rather than passing
the constructor as an argument:

function construct() {
var strings = [];
Array.prototype.forEach.apply(arguments, [ function(item,index) {
strings[index] = "arguments[" + index + "]";
} ]);
return eval("new this(" + strings.join(",") + ")");
}

var MyDate = function() {
return construct.apply(Date, arguments);
}

var mydate = new MyDate(2009,1,1);

alert(mydate); // print string representation of Feb 1, 2009

Thanks for your assistance on this.

David Mark

unread,
Jun 29, 2009, 11:01:52 PM6/29/09
to
On Jun 29, 9:45 pm, "David P. Caldwell" <ino...@inonit.com> wrote:
> David Mark wrote:
> > On Jun 29, 4:39 pm, "David P. Caldwell" <ino...@inonit.com> wrote:
> >> Is there a way to call a function as a constructor with an unknown
> >> number of arguments?  It seems like this should be simple, but I can't
> >> find any simple way to do it.
>
> >> Using the 'arguments' object, for example, it is easy to delegate a
> >> function call to another function:
>
> >> function myFunc() {
> >>       return delegate.apply(this, arguments);
>
> >> }
>
> >> But can I delegate a constructor call somehow?
>
> >> function MyConstructor() {
> >>       return new Delegate(...how to pass the arguments?)
>
> >> }
>
> > That's a factory, not a constructor.
>
> > function MyConstructor() {
> >    Delegate.apply(this, arguments):
> > }
>
> > var myConstructedObject = new MyConstructor();
>
> Maybe I wasn't clear enough -- that doesn't do what I'm looking for,
> though.  Consider this:
>
> var MyDate = function() {
>         Date.apply(this, arguments);
>
> }

Okay, you do want a factory.

>
> var mydate = new MyDate(2009,1,1);
>
> alert(mydate);
>
> I want the alert to show February 1, 2009.  But instead, it shows
> [object Object].

I get it.

>
> Using "return this;" doesn't improve the situation; same result.

Right.

>
> Using "return Date.apply(this, arguments)" is closer -- it calls Date as
> a *function* rather than as a constructor.  That function returns the
> current time as a string, per ECMA-262.  So the constructor returns
> "this" (constructors return this when you try to return a primitive type
> from them) and it shows the same result.
>
> So I'm able to call Date as a *function* using the arguments object
> (with apply) but I can't call it as a *constructor* with the arguments
> object.  So far as I know.

Well, you could eval as Thomas showed. I've never considered this
problem. Would be odd if eval were the only way.

Thomas 'PointedEars' Lahn

unread,
Jun 30, 2009, 3:55:35 AM6/30/09
to
David P. Caldwell wrote:
> Thomas 'PointedEars' Lahn wrote:
>> David P. Caldwell wrote:
>>> Is there a way to call a function as a constructor with an unknown
>>> number of arguments?
>> Yes, you can use eval().
>>
>>> and it seems like this should be a one-liner.
>> It is not a one-liner, but it is quite short.
>
> Yes, this approach works! I am still stunned there is no simpler way to
> do it (when apply and call are so simple for the non-constructor case).
>
> Here's the code I ended up using as my test case, which uses your
> approach, but uses 'this' in the construct function, rather than passing
> the constructor as an argument:
>
> function construct() {
> var strings = [];

`strings' is unwise an identifier; it doesn't talk about its purpose, and
confusion with `String' is likely.

> Array.prototype.forEach.apply(arguments, [ function(item,index) {
> strings[index] = "arguments[" + index + "]";
> } ]);

The built-in Array.prototype.forEach() does not support an array of Function
object references as argument, it throws a TypeError then; so that must be a
user-defined method. If it is a library-ism (e.g. Prototype.js or jQuery),
don't use either one. Always design your alternative methods so that they
work like the native ones.

<https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/forEach>

If you use Array.prototype.forEach(), which requires JavaScript 1.6, you
should consider using Array.prototype.map() from the same version instead,
as suggested, because it does not require a reference to the variable in the
mapping function.

> return eval("new this(" + strings.join(",") + ")");
> }

.join(",") is superfluous; as pointed out, the string representation of
an Array object (the result of its default .toString() method) is the
comma-separated list of its elements already.

function construct() {
var args = Array.prototype.map.call(arguments, function(e, i, a) {
return "arguments[" + i + "]";
});

return eval("new this(" + args + ")");
}

<https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/map>

> var MyDate = function() {
> return construct.apply(Date, arguments);
> }

The function expression appears to be unnecessary here; use a function
declaration instead. In any case, delimit your assignments by `;'.

> var mydate = new MyDate(2009,1,1);
> alert(mydate); // print string representation of Feb 1, 2009
>
> Thanks for your assistance on this.

You're welcome. But `MyDate' appears to be superfluous here:

function construct(Constructor, args)
{
return eval("new Constructor("
+ args.map(function(e, i, a) { return "arguments[" + (i + 1) + "]"; })
+ ")");
}

var mydate = construct(Date, [2009, 1, 1]);


PointedEars

David P. Caldwell

unread,
Jul 4, 2009, 11:55:44 AM7/4/09
to
> Thomas 'PointedEars' Lahn wrote:
>> David P. Caldwell wrote:

Thomas:

Thanks for your assistance on this.

>> Array.prototype.forEach.apply(arguments, [ function(item,index) {


>> strings[index] = "arguments[" + index + "]";
>> } ]);
>
> The built-in Array.prototype.forEach() does not support an array of Function
> object references as argument, it throws a TypeError then; so that must be a
> user-defined method. If it is a library-ism (e.g. Prototype.js or jQuery),
> don't use either one. Always design your alternative methods so that they
> work like the native ones.
>
> <https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/forEach>

I think you misread this -- the one-element array of functions is being
passed to apply, not to forEach. forEach() is just receiving a single
argument, the given function.

This code works in Firefox 3.5 with no libraries, and I don't see any
obvious reason it wouldn't work anywhere the 1.6 methods are defined.

> If you use Array.prototype.forEach(), which requires JavaScript 1.6, you
> should consider using Array.prototype.map() from the same version instead,
> as suggested, because it does not require a reference to the variable in the
> mapping function.

.forEach() passes the index as an argument, which makes it so that we
don't have to maintain a counter variable declared outside the scope.
With map, wouldn't you need something like this?

var index = 0;
Array.prototype.map.apply(arguments, [ function(item) {
return "arguments[" + String(index++) + "]";
}

>> var mydate = new MyDate(2009,1,1);
>> alert(mydate); // print string representation of Feb 1, 2009
>>
>> Thanks for your assistance on this.
>
> You're welcome. But `MyDate' appears to be superfluous here:
>
> function construct(Constructor, args)
> {
> return eval("new Constructor("
> + args.map(function(e, i, a) { return "arguments[" + (i + 1) + "]"; })
> + ")");
> }
>
> var mydate = construct(Date, [2009, 1, 1]);

True that it is superfluous in this toy example. But the whole point of
investigating this was to be able to use delegation conditionally:

var MyDate = function() {
if ( arguments.callee == this.constructor && ... certain kinds of
arguments ... ) {
// execute code specific to these arguments
} else {
// delegate to another constructor
return construct(Date, arguments);
}
}

Thomas 'PointedEars' Lahn

unread,
Jul 4, 2009, 12:21:15 PM7/4/09
to
David P. Caldwell wrote:
> > Thomas 'PointedEars' Lahn wrote:
>>> David P. Caldwell wrote:
>
> Thomas:
>
> Thanks for your assistance on this.

You're welcome.

>>> Array.prototype.forEach.apply(arguments, [ function(item,index) {
>>> strings[index] = "arguments[" + index + "]";
>>> } ]);
>> The built-in Array.prototype.forEach() does not support an array of Function
>> object references as argument, it throws a TypeError then; so that must be a
>> user-defined method. If it is a library-ism (e.g. Prototype.js or jQuery),
>> don't use either one. Always design your alternative methods so that they
>> work like the native ones.
>>
>> <https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/forEach>
>
> I think you misread this -- the one-element array of functions is being
> passed to apply, not to forEach. forEach() is just receiving a single
> argument, the given function.

Ahh, of course. In that case you should use call() and pass only the
Function object reference instead. That's one object less.

> This code works in Firefox 3.5 with no libraries, and I don't see any
> obvious reason it wouldn't work anywhere the 1.6 methods are defined.

ACK

>> If you use Array.prototype.forEach(), which requires JavaScript 1.6, you
>> should consider using Array.prototype.map() from the same version instead,
>> as suggested, because it does not require a reference to the variable in the
>> mapping function.
>

> ..forEach() passes the index as an argument, which makes it so that we

> don't have to maintain a counter variable declared outside the scope.
>
> With map, wouldn't you need something like this?
>
> var index = 0;
> Array.prototype.map.apply(arguments, [ function(item) {
> return "arguments[" + String(index++) + "]";
> }

No.

,-<https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/forEach>
|
| [...]
| array.forEach(callback[, thisObject]);
| [...]
| callback is invoked with three arguments: the value of the element, the
| index of the element, and the Array object being traversed.

,-<https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/map>
|
| [...]
| var mappedArray = array.map(callback[, thisObject]);
| [...]
| callback is invoked with three arguments: the value of the element, the
| index of the element, and the Array object being traversed.

The difference is: map() returns a new Array and does not modify the
existing one (trying to do so in the callback is recommended against).

> the whole point of investigating this was to be able to use delegation
> conditionally:
>
> var MyDate = function() {
> if ( arguments.callee == this.constructor && ... certain kinds of
> arguments ... ) {
> // execute code specific to these arguments
> } else {
> // delegate to another constructor
> return construct(Date, arguments);
> }
> }

ACK. But what do you need this for?


PointedEars
--
var bugRiddenCrashPronePieceOfJunk = (
navigator.userAgent.indexOf('MSIE 5') != -1
&& navigator.userAgent.indexOf('Mac') != -1
) // Plone, register_function.js:16

David P. Caldwell

unread,
Jul 5, 2009, 8:58:59 PM7/5/09
to
Thomas 'PointedEars' Lahn wrote:
> David P. Caldwell wrote:
>> > Thomas 'PointedEars' Lahn wrote:
>>>> David P. Caldwell wrote:
>> Thomas:
>>
>> Thanks for your assistance on this.
>
> You're welcome.

Thanks again. :)

>> ..forEach() passes the index as an argument, which makes it so that we
>> don't have to maintain a counter variable declared outside the scope.
>>
>> With map, wouldn't you need something like this?
>>
>> var index = 0;
>> Array.prototype.map.apply(arguments, [ function(item) {
>> return "arguments[" + String(index++) + "]";
>> }
>
> No.
>

> | var mappedArray = array.map(callback[, thisObject]);
> | [...]
> | callback is invoked with three arguments: the value of the element, the
> | index of the element, and the Array object being traversed.

I wasn't aware that map() receives the same callback as forEach().
Given that, map() makes much more sense.

>> the whole point of investigating this was to be able to use delegation
>> conditionally:
>>
>> var MyDate = function() {
>> if ( arguments.callee == this.constructor && ... certain kinds of
>> arguments ... ) {
>> // execute code specific to these arguments
>> } else {
>> // delegate to another constructor
>> return construct(Date, arguments);
>> }
>> }
>
> ACK. But what do you need this for?

It will allow me to silently decorate an existing constructor function
in order to take an unanticipated set of arguments, without perturbing
existing callers who were writing to the old interface, or modifying the
original code.

-- David.

0 new messages