Tworzenie instancji obiektu JavaScript przez wywołanie prototype.konstruktor.zastosuj

Zacznę od konkretnego przykładu tego, co próbuję zrobić.

Mam tablicę roku, miesiąca, dnia, godziny, minuty, sekundy i milisekundy składników w postaci [ 2008, 10, 8, 00, 16, 34, 254 ]. Chciałbym utworzyć instancję obiektu Date używając następującego standardowego konstruktora:

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

Jak mogę przekazać tablicę do tego konstruktora, aby uzyskać nową instancję Date? [ Update: moje pytanie wykracza poza ten konkretny przykład. Chciałbym ogólne rozwiązanie dla wbudowanego JavaScript klasy takie jak Date, Array, RegExp, itp. których konstruktorzy są poza moim zasięgiem. ]

Staram się zrobić coś takiego:

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

Prawdopodobnie potrzebuję gdzieś tam "new". Powyższe zwraca bieżący czas, tak jakbym nazwał "(new Date()).toString()". Przyznaję również, że mogę być całkowicie w złym kierunku z powyższym :)

Uwaga: brak eval() i brak dostępu do elementów tablicy jeden po drugim, proszę. Jestem prawie pewien, że powinienem być możliwość użycia tablicy tak, jak jest.


Aktualizacja: Dalsze Eksperymenty

Ponieważ nikt jeszcze nie był w stanie wymyślić odpowiedzi roboczej, zrobiłem więcej zabawy wokół. Oto nowe odkrycie.

Mogę to zrobić z własną klasą:

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!

Ale to nie działa z klasą intrinsic Date:

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

Nie działa też z liczbą:

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

Może to po prostu nie jest możliwe z obiektami wewnętrznymi? Testuję z Firefoksem BTW.

Author: Ates Goral, 2008-10-08

13 answers

Zrobiłem więcej własnych badań i doszedłem do wniosku, że jest to niemożliwy wyczyn , ze względu na sposób implementacji klasy Date.

Sprawdzałem kod źródłowy SpiderMonkey aby zobaczyć, jak data została zaimplementowana. Myślę, że wszystko sprowadza się do następujących kilku linijek:

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)

Gdy Date jest używana jako funkcja (albo jako Date() lub Date.prototype.constructor(), które są dokładnie tym samym), domyślnie zwraca bieżący czas jako łańcuch w format lokalny. Jest to bez względu na argumenty, które są przekazywane w:

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
Nie sądzę, żeby na poziomie JS można było coś zrobić, żeby to obejść. I to chyba koniec moich poszukiwań w tym temacie.

Zauważyłem też coś ciekawego:

    /* 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 jest instancją daty o wewnętrznej wartości NaN, a zatem

alert(Date.prototype); // Always returns "Invalid Date"
                       // on Firefox, Opera, Safari, Chrome
                       // but not Internet Explorer
IE nas nie zawodzi. Robi to nieco inaczej i prawdopodobnie ustawia wewnętrzną wartość na -1 więc ta Data.prototype zawsze zwraca datę nieco przed epoką.

Update

W końcu zajrzałem do samego ECMA - 262 i okazało się, że to, co próbuję osiągnąć (z obiektem Date), jest z definicji niemożliwe: {]}

15.9.2 Konstruktor daty wywołany jako funkcja

Gdy Data jest nazywana funkcja zamiast jako konstruktor, zwraca łańcuch reprezentujący aktualny czas (UTC).

Uwaga funkcja wywołanie Date(…) nie jest równoznaczne z wyrażenie tworzenia obiektów new Date(…) z tymi samymi argumentami.

15.9.2.1 Data ([Rok [, Miesiąc [, Data [, Godziny [, Minuty [, Sekundy [, ms ] ] ] ] ] ] ] )

Wszystkie argumenty są opcjonalne; wszelkie argumenty dostarczane są akceptowane, ale są całkowicie zignorowany. Ciąg jest utworzone i zwrócone tak jakby przez wyrażenie (new Date()).toString().

 63
Author: Ates Goral,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-10 02:03:37

Nie nazwałbym tego eleganckim, ale w moich testach (FF3, Saf4, IE8) działa:

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

Zamiast tego:

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

Spróbuj tego:

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

 14
Author: Cowboy Ben Alman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-22 20:31:02

Tak można rozwiązać konkretny przypadek: -

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)

Jednak szukasz bardziej ogólnego rozwiązania. Zalecanym kodem do tworzenia metody konstruktora jest return this.

Stąd: -

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

Można skonstruować: -

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

Jednak nie wszystkie implementacje działają w ten sposób, nie tylko dlatego, że łańcuch prototypów byłby błędny:-

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
Author: AnthonyWJones,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-08 07:56:14

Jest mniej niż elegancki, ale oto rozwiązanie:

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)
    }
}
Sposób, w jaki to działa, powinien być dość oczywisty. Tworzy funkcję poprzez generowanie kodu. Ten przykład ma ustaloną liczbę parametrów dla każdego konstruktora, który tworzysz, ale i tak jest to przydatne. Przez większość czasu masz przynajmniej maksymalną liczbę argumentów na myśli. Jest to również lepsze niż niektóre z innych przykładów, ponieważ pozwala wygenerować kod raz, a następnie ponownie go użyć. Wygenerowany kod korzysta z funkcji zmiennej argument javascript, w ten sposób można uniknąć konieczności nazywania każdego parametru (lub przeliterowania ich na liście i przekazywania argumentów do generowanej funkcji). Oto działający przykład:
var dateConstructor = new GeneratedConstructor("Date", 3)
dateConstructor.createObject( [ 1982, 03, 23 ] )

Zwróci to:

Pią Kwi 23 1982 00:00:00 GMT-0800 (PST)

Rzeczywiście jest nadal...trochę brzydki. Ale przynajmniej wygodnie ukrywa ten bałagan i nie zakłada, że skompilowany kod może dostać śmieci zebrane (ponieważ może to zależeć od implementacji i jest prawdopodobnym obszarem dla błędów). Pozdrawiam, Scott S. McCoy
 4
Author: 2 revsScott S McCoy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-04-24 06:10:02

Tak to się robi:

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]);

Będzie działać z dowolnym konstruktorem, a nie tylko wbudowanymi lub konstruktorami, które mogą podwoić się jako funkcje (jak Date).

Jednak wymaga Ecmascript 5 .funkcja bind. Podkładki dystansowe prawdopodobnie nie będą działać poprawnie.

Przy okazji, jedna z innych odpowiedzi sugeruje zwrócenie this z konstruktora. Może to bardzo utrudnić rozszerzenie obiektu przy użyciu klasycznego dziedziczenia, więc uznałbym to za antypattern.
 3
Author: kybernetikos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-25 16:28:26

Będzie działać z operatorem spreadu ES6. Po prostu:

const arr = [2018, 6, 15, 12, 30, 30, 500];
const date = new Date(...arr);

console.log(date);
 1
Author: Wysher,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-22 20:17:24

W składni ES6 istnieją co najmniej 2 metody, aby to osiągnąć:

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

// with the spread operator
var d1 = new Date(...comps);

// with Reflect.construct
var d2 = Reflect.construct(Date, comps);

console.log('d1:', d1, '\nd2:', d2);
// or more readable:
console.log(`d1: ${d1}\nd2: ${d2}`);
 1
Author: Scott Rudiger,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-02 03:46:02

Możesz to zrobić z flagrant, flagrant nadużycie 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);
}

Przykładowe użycie:

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

Enjoy=).

 0
Author: Claudiu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-17 06:06:16
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
Author: ZERONETA,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-25 10:30:04

Wiem, że minęło dużo czasu, ale mam prawdziwą odpowiedź na to pytanie. To nie jest niemożliwe. Zobacz https://gist.github.com/747650 dla ogólnego rozwiązania.

var F = function(){};
F.prototype = Date.prototype;
var d = new F();
Date.apply(d, comps);
 -1
Author: Michael Ficarra,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-05 17:36:40

Oto inne rozwiązanie:

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]) )
 -1
Author: Quadroid,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-01 15:34:05

Edytowane

Sorry, byłam pewna, że tak zrobiłem lata temu, teraz będę się trzymać:

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

Edit:

Ale pamiętaj, że obiekt JavaScript Date-object używa indeksów przez miesiące, więc powyższa tablica oznacza

Listopad 8 2008 00:16:34:254

 -2
Author: roenving,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-08 08:24:22
var comps = [ 2008, 10, 8, 00, 16, 34, 254 ];
var d = eval("new Date(" + comps.join(",") + ");");
 -3
Author: harley.333,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-08 20:11:16