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.
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ównew 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()
.
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 ) );
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
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:
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. McCoyPią Kwi 23 1982 00:00:00 GMT-0800 (PST)
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óceniethis
z konstruktora. Może to bardzo utrudnić rozszerzenie obiektu przy użyciu klasycznego dziedziczenia, więc uznałbym to za antypattern.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);
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}`);
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=).
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])
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);
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]) )
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
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(",") + ");");
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