Topic: Instantiating a JavaScript object by calling prototype.constructor.apply Pages that link to <a href="https://ozoneasylum.com/backlink?for=30593" title="Pages that link to Topic: Instantiating a JavaScript object by calling prototype.constructor.apply" rel="nofollow" >Topic: Instantiating a JavaScript object by calling prototype.constructor.apply\

 
Author Thread
MaGnA
Nervous Wreck (II) Inmate

From: Toronto
Insane since: Jan 2008

IP logged posted posted 10-08-2008 21:20 Edit Quote

Fellow inmates,

I don't know if you've heard of http://stackoverflow.com. It's a question/answer/discussion programmer community, currently in public beta. But it's no expertsexchange.com! It has an elite crowd of users, carrying out fairly interesting technical/philosophical discussions. Oh wait, that reminds me of a familiar forum

For a bit of code I was working on last night, I ran into a problem which my JS hax0ring skills ran short of solving. I ended up using a different approach, but I'm still pursuing the answer to my initial question. I wanted to give stackoverflow.com a shot first, but unfortunately no one (as of the posting of this thread) has been able to post a satisfactory answer to my question. I think I have a better shot at getting an answer here at OA. Here's the original question that I posted at stackoverflow.com:

--

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

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

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



How can I pass my array to this constructor to get a new Date instance? My question actually extends beyond this specific example. I'd like a general solution for built-in JavaScript classes like Date, Array, RegExp, etc. whose constructors are beyond my reach.

I'm trying to do something like the following:

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



I probably need a "new" in there somewhere. The above just returns the current time as if I had called "new Date()". I also acknowledge that I may be completely in the wrong direction with the above

Note: No eval() and no accessing the array items one by one, please. I'm pretty sure I should be able to use the array as is.



(Edited by MaGnA on 10-08-2008 21:21)

MaGnA
Nervous Wreck (II) Inmate

From: Toronto
Insane since: Jan 2008

IP logged posted posted 10-09-2008 05:11 Edit Quote

I've done a few more experiments:

I can do this with my own class:

code:
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 doesn't work with the intrinsic Date class:

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



Neither does it work with Number:

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



Maybe this just isn't possible with intrinsic objects? I'm testing with Firefox BTW.

esskay
Bipolar (III) Inmate

From:
Insane since: Jan 2005

IP logged posted posted 10-09-2008 06:05 Edit Quote

Very interesting topic. I've been struggling to wrap my head around JS OOP for some time now - It seems pretty wonky compared to other high level languages. Anyway, I've been searching google and experimenting for about the last hour trying to find something. The best I could come up with is that I think prototype.constructor is not the right thing. There is a method called constructor that does return some weird garbage that appears to be related to initialization, however I don't believe that it is the actual constructor. I think it's too late after you've called new ObjectName() because the constructor has already been invoked: you can't change it now.

So I don't have a direct answer for you because I don't yet understand it fully, but I can suggest this different angle of attack: try changing the definition of the Date object BEFORE instantiating it. Something like this:

code:
// The problem here is that constructor seems to not be the correct method
// to override the actual class constructor - so what is the right way?
Date.prototype.constructor = function(myargs) {

	// This calls the original constructor with myargs as an array, just like you requested
	Date.apply(this, myargs);

}

var comps = [ 2008, 10, 8, 00, 16, 34, 254 ];

// NOW we try to instantiate a Date object after we've altered the behavior through the prototype...
var d = new Date(comps);

var year = d.getYear();
var month = d.getMonth();
var day = d.getDate();



To be clear: the above does not work, but I think the approach is worth looking at...

Regards,
- SK

poi
Paranoid (IV) Inmate

From: Norway
Insane since: Jun 2002

IP logged posted posted 10-09-2008 09:23 Edit Quote

I gave it a shot yesterday evening, in Opera 9.6 and FireFox 3.0.3 and came to the same conclusion as MaGnA.

A really quick look at the ECMA 262 spec did not reveal anything specical about apply and native functions and constructors.

I'll ask my fellow JS devs at work today, and if anything fails I could ask the devs of the JS engine.



(Edited by poi on 10-09-2008 09:33)

MaGnA
Nervous Wreck (II) Inmate

From: Toronto
Insane since: Jan 2008

IP logged posted posted 10-10-2008 05:10 Edit Quote

Thanks esskay, poi for looking into this.

esskay: I had tried intercepting the constructor like you did, to no avail. The reason is explained below.

poi:: Thanks for checking the spec. Any word from the devs?

I've done more investigation of my own and came up with the conclusion that this is an impossible feat, due to how the Date class is implemented.

I've inspected the SpiderMonkey source code to see how Date was implemented. I think it all boils down to the following few lines:

code:
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 are exactly the same thing), it defaults to returning the current time as a string in the locale format. I don't think there's anything that can be done at the JS level to circumvent this. And this is probably the end of my pursuit in this topic.

I've also noticed something interesting:

code:
/* 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 a Date instance with the internal value of NaN and therefore,

code:
alert(Date.prototype); // Always returns "Invalid Date" on FF, Opera, Safari, Chrome but not IE



*SURPRISE* *SURPRISE* *SURPRISE*
IE does things a bit differently and I think sets the internal value to -1 so that Date.prototype is a date slightly before epoch.
*END SURPRISE* *END SURPRISE* *END SURPRISE*



(Edited by MaGnA on 10-10-2008 05:19)

liorean
Paranoid (IV) Inmate

From: Umeå, Sweden
Insane since: Sep 2004

IP logged posted posted 10-10-2008 05:42 Edit Quote

The built-in objects are different from native functions because they do not necessarily run the same code when called as constructors as they do when called as functions. Native functions (i.e. ECMAScript author declared functions) on the other hand always run the same code during function calls as during constructor calls.



Object, Function, Array and RegExp should behave identically when called as functions compared to when called as constructors. Well, both Object and RegExp have exceptions, but the principle holds remarkably well as a general rule.

Number, Boolean and String produce primitives when run as functions and wrapper objects around primitives when called as constructors.

Date ignores it's arguments when called as a function, always returns a string with current time. Date when called as a constructor without arguments returns a new Date object for the current time. Date when called as a constructor with a single argument uses the same mechanism as Date.parse. Date when called as a constructor with a more than a single argument will behave as if you had called it with no arguments, and then had called setYear with the first three arguments and setHours with the last four arguments, substituting 0 for missing arguments except the third argument which is substituted with 1 instead.

--
var Liorean = {
abode: "http://web-graphics.com/",
profile: "http://codingforums.com/member.php?u=5798"};

MaGnA
Nervous Wreck (II) Inmate

From: Toronto
Insane since: Jan 2008

IP logged posted posted 10-10-2008 06:13 Edit Quote

liorean: Thanks for beautifully summing it all up in just a few paragraphs!

Then we could probably say Number, Boolean and String syntactically look and work just like a C++-style cast -- SomeType(arg).

Also, Object seems to be doing something interesting when used as a function; it will create a wrapper object, with the most appropriate class for the given argument:

code:
Object(42).toSource() -> (new Number(42))
Object("hi").toSource() -> (new String("hi"))
Object(false).toSource() -> (new Boolean(false))



I had never used JS classes as functions like this before. It has been a good find for me



(Edited by MaGnA on 10-10-2008 06:14)



Post Reply
 
Your User Name:
Your Password:
Login Options:
 
Your Text:
Loading...
Options:


« BackwardsOnwards »

Show Forum Drop Down Menu