Создание экземпляра объекта JavaScript путем вызова prototype.конструктор.применять
позвольте мне начать с конкретного примера того, что я пытаюсь сделать.
у меня есть массив год, месяц, день, час, минута, секунда и миллисекундные компоненты в виде [ 2008, 10, 8, 00, 16, 34, 254 ]
. Я хотел бы создать экземпляр объекта Date с помощью следующего стандартного конструктора:
new Date(year, month, date [, hour, minute, second, millisecond ])
как я могу передать свой массив в этот конструктор, чтобы получить новый экземпляр даты? [ обновление: мой вопрос на самом деле выходит за рамки этого конкретного примера. Я бы вообще решение для встроенных классов JavaScript, таких как дата, массив, регулярное выражение и т. д. чьи конструкторы вне моей досягаемости. ]
Я пытаюсь сделать что-то вроде следующего:
var comps = [ 2008, 10, 8, 00, 16, 34, 254 ];
var d = Date.prototype.constructor.apply(this, comps);
мне, наверное, нужен "new
" где-то там. Выше просто возвращает текущее время как если бы я обозвал "(new Date()).toString()
". Я также признаю, что я могу быть полностью в неправильном направлении с вышеизложенным:)
Примечание: нет eval()
и нет доступа к элементам массива по одному, пожалуйста. Я уверен, что я должен быть в состоянии использовать массив как есть.
Обновление: Дальнейшие Эксперименты
поскольку никто еще не смог придумать рабочий ответ, я сделал больше игр вокруг. Вот вам новое открытие.
Я могу сделать это с моим собственным классом:
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!
но он не работает с внутренним классом даты:
var d = new Date();
Date.prototype.constructor.call(d, 1000);
document.write(d); // Still returns current time :(
он также не работает с Номер:
var n = new Number(42);
Number.prototype.constructor.call(n, 666);
document.write(n); // Returns 42
может быть, это просто невозможно с внутренними объектами? Я тестирую с Firefox, кстати.
13 ответов:
я провел больше собственных исследований и пришел к выводу, что это невозможно, из-за того, как реализуется класс Date.
я осмотрел SpiderMonkey исходный код, чтобы увидеть, как дата была реализована. Я думаю, что все это сводится к следующим нескольким строкам:
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)
когда дата используется в качестве функции (либо как
Date()
илиDate.prototype.constructor()
, которые точно то же самое), он по умолчанию возвращается текущее время в виде строки в формате локали. Это независимо от любых аргументов, которые передаются в: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
я не думаю, что есть что-то, что можно сделать на уровне JS, чтобы обойти это. И это, вероятно, конец моего преследования в этой теме.
я тоже заметил кое-что интересное:
/* 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
- это экземпляр даты с внутренним значениемNaN
и поэтомуalert(Date.prototype); // Always returns "Invalid Date" // on Firefox, Opera, Safari, Chrome // but not Internet Explorer
IE не разочаровывает нам. Он делает вещи немного по-другому и, вероятно, устанавливает внутреннее значение
-1
так что дата.прототип всегда возвращает дату чуть раньше эпохи.
обновление
я наконец-то покопался в самом ECMA-262, и оказалось, что я пытаюсь достичь (с объектом даты) - по определению-невозможно:
15.9.2 конструктор даты вызывается как функция
когда дата называется функция, а не как конструктор, он возвращает строку, представляющую текущее время (UTC).
Примечание функции звоните
Date(…)
не эквивалентно выражение для создания объектаnew Date(…)
с теми же аргументами.15.9.2.1 дата ([ год [, месяц [, дата [, часы [, минуты [, секунды [, ms ] ] ] ] ] ] ] )
все аргументы являются необязательными; каких-либо аргументов поставили принято, но полностью игнорировать. Строка созданный и возвращенный как будто выражение
(new Date()).toString()
.
Я бы вряд ли назвал это элегантным, но в моем тестировании (FF3, Saf4, IE8) это работает:
var arr = [ 2009, 6, 22, 10, 30, 9 ];
вместо этого:
var d = new Date( arr[0], arr[1], arr[2], arr[3], arr[4], arr[5] );
попробуйте это:
var d = new Date( Date.UTC.apply( window, arr ) + ( (new Date()).getTimezoneOffset() * 60000 ) );
вот как вы могли бы решить конкретный случай: -
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)
однако вы ищете более общее решение. Рекомендуемый код для создания метода конструктора должен иметь его
return this
.отсюда:-
function Target(x , y) { this.x = x, this.y = y; return this; }
может быть построена :-
var x = Target.apply({}, [1, 2]);
однако не все реализации работают таким образом не в последнюю очередь потому, что цепочка прототипов была бы неправильной:-
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
Это менее элегантно, но вот решение:
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) } }
как это работает должно быть довольно очевидно. Он создает функцию через генерацию кода. Этот пример имеет фиксированное число параметров для каждого конструктора можно создать, но это полезно в любом случае. Большую часть времени у вас есть по крайней мере максимальное количество аргументов в виду. Это также лучше, чем некоторые другие примеры здесь, потому что это позволяет генерировать код один раз, а затем повторно использовать его. Код это генерируется использует функцию переменных аргументов javascript, таким образом, вы можете избежать необходимости называть каждый параметр (или прописывать их в списке и передавать аргументы в функцию, которую вы создаете). Вот рабочий пример:
var dateConstructor = new GeneratedConstructor("Date", 3) dateConstructor.createObject( [ 1982, 03, 23 ] )
это вернет следующее:
Пт 23 апреля 1982 00:00: 00 GMT-0800 (PST)
Это действительно все равно...немного некрасиво. Но это по крайней мере удобно скрывает беспорядок и не предполагает этот скомпилированный код сам может собирать мусор (поскольку это может зависеть от реализации и является вероятной областью для ошибок).
Ура, Скотт С. Маккой
вот как вы это делаете:
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]);
Он будет работать с любым конструктором, а не только встроенными или конструкторами, которые могут выполнять функции (например, Date).
однако для этого требуется Ecmascript 5 .функция bind. Прокладки, вероятно, не будут работать правильно.
кстати, один из других ответов предлагает вернуться
this
из конструктора. Это может очень затруднить расширение объекта с использованием классического наследования, поэтому я бы рассмотрел его антимодель.
Он будет работать с оператором распространения ES6. Вы просто:
const arr = [2018, 6, 15, 12, 30, 30, 500]; const date = new Date(...arr); console.log(date);
с синтаксисом ES6, есть по крайней мере 2 метода для достижения этой цели:
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}`);
Вы можете сделать это с грубым, грубо злоупотребление 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); }
пример использования:
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
наслаждаемся =).
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])
Я знаю, что это было давно, но у меня есть реальный ответ на этот вопрос. Это далеко не невозможно. См.https://gist.github.com/747650 для общего решения.
var F = function(){}; F.prototype = Date.prototype; var d = new F(); Date.apply(d, comps);
вот еще одно решение:
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]) )
редактировать
Извините, я был уверен, что сделал это так много лет назад, прямо сейчас я буду придерживаться:
ВАР д = новая дата(ком[0],конкурсы[1],конкурсы[2],конкурсы[3],конкурсы[4],конкурсы[5],конкурсы[6]);
Edit:
но помните, что объект даты javascript использует индексы в течение нескольких месяцев, поэтому приведенный выше массив означает
ноября 8 2008 00:16:34:254