Creating a JavaScript object by calling prototype.constructor.apply

Let me start with a concrete example of what I'm trying to do.

I have an array of components of the year, month, day, hour, minute, second and millisecond in the form [ 2008, 10, 8, 00, 16, 34, 254 ] . I would like to instantiate a Date object using the following standard constructor:

 new Date(year, month, date [, hour, minute, second, millisecond ]) 

How to pass an array to this constructor to get a new Date instance? [ Update . My question really goes beyond this specific example. I would like to get a general solution for inline JavaScript classes such as Date, Array, RegExp, etc., whose constructors are not available. ]

I am trying to do something like the following:

 var comps = [ 2008, 10, 8, 00, 16, 34, 254 ]; var d = Date.prototype.constructor.apply(this, comps); 

I probably need " new ". The above just returns the current time, as if I called " (new Date()).toString() ". I also admit that I can be completely in the wrong direction with the above :)

Note : There is no eval() and without access to the elements of the array one by one, please. I am sure that I should use the array as is.




Update: further experiments

Since no one could find a working answer, I played more. Here is a new discovery.

I can do this with my own class:

 function Foo(a, b) { this.a = a; this.b = b; this.toString = function () { return this.a + this.b; }; } var foo = new Foo(1, 2); Foo.prototype.constructor.apply(foo, [4, 8]); document.write(foo); // Returns 12 -- yay! 

But it does not work with the inner class Date:

 var d = new Date(); Date.prototype.constructor.call(d, 1000); document.write(d); // Still returns current time :( 

It also does not work with Number:

 var n = new Number(42); Number.prototype.constructor.call(n, 666); document.write(n); // Returns 42 

Maybe this is simply not possible with internal objects? I am testing using Firefox BTW.

+52
javascript reflection
Oct 08 '08 at 4:32
source share
11 answers

I did more of my own research and came to the conclusion that this is an impossible feat due to the way the Date class is implemented.

I looked at the source code for SpiderMonkey to see how the date was implemented. I think it all comes down to the following lines:

 static JSBool Date(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { jsdouble *date; JSString *str; jsdouble d; /* Date called as function. */ if (!(cx->fp->flags & JSFRAME_CONSTRUCTING)) { int64 us, ms, us2ms; jsdouble msec_time; /* NSPR 2.0 docs say 'We do not support PRMJ_NowMS and PRMJ_NowS', * so compute ms from PRMJ_Now. */ us = PRMJ_Now(); JSLL_UI2L(us2ms, PRMJ_USEC_PER_MSEC); JSLL_DIV(ms, us, us2ms); JSLL_L2D(msec_time, ms); return date_format(cx, msec_time, FORMATSPEC_FULL, rval); } /* Date called as constructor. */ // ... (from here on it checks the arg count to decide how to create the date) 

When Date is used as a function (either as Date() or Date.prototype.constructor() , which is exactly the same), by default it returns the current time as a string in locale format. This is independent of any arguments that are passed in:

 alert(Date()); // Returns "Thu Oct 09 2008 23:15:54 ..." alert(typeof Date()); // Returns "string" alert(Date(42)); // Same thing, "Thu Oct 09 2008 23:15:54 ..." alert(Date(2008, 10, 10)); // Ditto alert(Date(null)); // Just doesn't care 

I don't think there is anything that can be done at the JS level to get around this. And this is probably the end of my persecution on this topic.

I also noticed something interesting:

  /* Set the value of the Date.prototype date to NaN */ proto_date = date_constructor(cx, proto); if (!proto_date) return NULL; *proto_date = *cx->runtime->jsNaN; 

Date.prototype is an instance of Date with an internal value of NaN and therefore

 alert(Date.prototype); // Always returns "Invalid Date" // on Firefox, Opera, Safari, Chrome // but not Internet Explorer 

IE will not disappoint us. It does something different and probably sets the internal value to -1 , so Date.prototype always returns the date a little before the era.




Update

I finally dug in ECMA-262, and it turns out that I'm trying to achieve (with a Date object) - by definition - impossible:

15.9.2 Date constructor called as a function

When Date is called as and not as a constructor, it returns a string representing the current time (UTC).

NOTE The function calling Date(…) not equivalent to the expression creating the new Date(…) object with the same arguments.

15.9.2.1 Date ([year [, month [, date [, hours [, minutes [, seconds [, ms]]]]]]])

All arguments are optional; any arguments are supplied, but completely ignored. The string is created and returned as if the expression (new Date()).toString() .

+63
Oct 19 '08 at 21:58
source share

I would not call it elegant, but in my testing (FF3, Saf4, IE8) it works:

var arr = [ 2009, 6, 22, 10, 30, 9 ];

Instead of this:

var d = new Date( arr[0], arr[1], arr[2], arr[3], arr[4], arr[5] );

Try the following:

var d = new Date( Date.UTC.apply( window, arr ) + ( (new Date()).getTimezoneOffset() * 60000 ) );

+14
Jul 22 '09 at 20:31
source share

Here's how you can solve a specific case: -

 function writeLn(s) { //your code to write a line to stdout WScript.Echo(s) } var a = [ 2008, 10, 8, 00, 16, 34, 254 ] var d = NewDate.apply(null, a) function NewDate(year, month, date, hour, minute, second, millisecond) { return new Date(year, month, date, hour, minute, second, millisecond); } writeLn(d) 

However, you are looking for a more general solution. Recommended code for creating a constructor method should have its return this .

In this way: -

 function Target(x , y) { this.x = x, this.y = y; return this; } 

can be built: -

 var x = Target.apply({}, [1, 2]); 

However, not all implementations work this way, not least because the prototype chain would be wrong: -

 var n = {}; Target.prototype = n; var x = Target.apply({}, [1, 2]); var b = n.isPrototypeOf(x); // returns false var y = new Target(3, 4); b = n.isPrototypeOf(y); // returns true 
+8
Oct 08 '08 at 7:30
source share

This is less elegant, but here is the solution:

 function GeneratedConstructor (methodName, argumentCount) { var params = [] for (var i = 0; i < argumentCount; i++) { params.push("arguments[" + i + "]") } var code = "return new " + methodName + "(" + params.join(",") + ")" var ctor = new Function(code) this.createObject = function (params) { return ctor.apply(this, params) } } 

How this works should be pretty obvious. It creates a function by generating code. This example has a fixed number of parameters for each constructor created, but it is useful in any case. In most cases, you have as many arguments as possible. It is also better than some other examples here because it allows you to generate code once and then reuse it. The generated code uses the javascript argument variable function, so you can omit each parameter (or write them to a list and pass arguments to the function that you generate). Here is a working example:

 var dateConstructor = new GeneratedConstructor("Date", 3) dateConstructor.createObject( [ 1982, 03, 23 ] ) 

This will return the following:

Fri Apr 23 1982 00:00:00 GMT-0800 (PST)

It really is still ... a little ugly. But at least it conveniently hides the clutter and does not assume that the compiled code itself can receive garbage collection (since this may be implementation dependent and is a likely area for errors).

Cheers, Scott S. McCoy

+4
Apr 20 '09 at 5:58
source share

Here's how you do it:

 function applyToConstructor(constructor, argArray) { var args = [null].concat(argArray); var factoryFunction = constructor.bind.apply(constructor, args); return new factoryFunction(); } var d = applyToConstructor(Date, [2008, 10, 8, 00, 16, 34, 254]); 

It will work with any constructor, not just built-in or constructors that can work as functions (e.g. Date).

However, this requires a function. Gaskets are likely to work incorrectly.

By the way, one of the other answers suggests returning this from the constructor. This can make it difficult to expand an object using classical inheritance, so I consider it an anti-pattern.

+3
Jan 17 '13 at 9:55
source share

You can do this with blatant, blatant abuse of eval:

 var newwrapper = function (constr, args) { var argHolder = {"c": constr}; for (var i=0; i < args.length; i++) { argHolder["$" + i] = args[i]; } var newStr = "new (argHolder['c'])("; for (var i=0; i < args.length; i++) { newStr += "argHolder['$" + i + "']"; if (i != args.length - 1) newStr += ", "; } newStr += ");"; return eval(newStr); } 

Sample Usage:

 function Point(x,y) { this.x = x; this.y = y; } var p = __new(Point, [10, 20]); alert(px); //10 alert(p instanceof Point); //true 

enjoy =).

0
Feb 17 '10 at 6:06
source share
 function gettime() { var q = new Date; arguments.length && q.setTime( ( arguments.length === 1 ? typeof arguments[0] === 'number' ? arguments[0] : Date.parse( arguments[0] ) : Date.UTC.apply( null, arguments ) ) + q.getTimezoneOffset() * 60000 ); return q; }; gettime(2003,8,16) gettime.apply(null,[2003,8,16]) 
0
Jun 25 '15 at 10:19
source share

I know that it was a long time ago, but I have a real answer to this question. This is far from impossible. See https://gist.github.com/747650 for a general solution.

 var F = function(){}; F.prototype = Date.prototype; var d = new F(); Date.apply(d, comps); 
-one
Apr 05 2018-11-11T00:
source share

Here is another solution:

 function createInstance(Constructor, args){ var TempConstructor = function(){}; TempConstructor.prototype = Constructor.prototype; var instance = new TempConstructor; var ret = Constructor.apply(instance, args); return ret instanceof Object ? ret : instance; } console.log( createInstance(Date, [2008, 10, 8, 00, 16, 34, 254]) ) 
-one
Aug 01 '12 at 15:34
source share

Edited

Sorry, I was sure I did this so many years ago, now I will stick to:

var d = new Date (comps [0], comps [1], comps [2], comps [3], comps [4], comps [5], comps [6]);

Edit:

But remember, the javascript data object has been using indexes for several months, so the above array means

November 8, 2008 00: 16: 34: 254

-2
Oct 08 '08 at 7:50
source share
 var comps = [ 2008, 10, 8, 00, 16, 34, 254 ]; var d = eval("new Date(" + comps.join(",") + ");"); 
-3
. 08 Oct '08 at 20:11
source share



All Articles