Подтвердить что ты не робот

Создание объекта JavaScript путем вызова prototype.constructor.apply

Позвольте мне начать с конкретного примера того, что я пытаюсь сделать.

У меня есть массив компонентов года, месяца, дня, часа, минуты, секунды и миллисекунды в форме [ 2008, 10, 8, 00, 16, 34, 254 ]. Я хотел бы создать экземпляр объекта Date с помощью следующего стандартного конструктора:

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

Как передать массив в этот конструктор, чтобы получить новый экземпляр Date? [ Обновление. Мой вопрос действительно выходит за рамки этого конкретного примера. Я хотел бы получить общее решение для встроенных классов JavaScript, таких как Date, Array, RegExp и т.д., Чьи конструкторы недоступны. ]

Я пытаюсь сделать что-то вроде следующего:

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!

Но он не работает с внутренним классом Date:

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

Он также не работает с Number:

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

Может быть, это просто невозможно с внутренними объектами? Я тестирую с помощью Firefox BTW.

4b9b3361

Ответ 1

Я сделал больше собственных исследований и пришел к выводу, что это невозможный подвиг из-за того, как реализован класс 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() или 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 - это экземпляр Date с внутренним значением NaN и, следовательно,

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

IE не разочарует нас. Он делает что-то по-другому и, вероятно, устанавливает внутреннее значение -1, так что Date.prototype всегда возвращает дату немного до эпохи.


Update

Наконец-то я выкопал в ECMA-262, и получается, что я пытаюсь достичь (с объектом Date) - по определению - невозможно:

15.9.2 Конструктор даты, вызываемый как функция

Когда Date вызывается как а не как конструктор, он возвращает строку, представляющую текущее время (UTC).

ПРИМЕЧАНИЕ Функция вызов Date(…) не эквивалентен выражение создания объекта new Date(…)с теми же аргументами.

15.9.2.1 Дата ([год [, месяц [, дата [, часы [, минуты [, секунды [, ms]]]]]]])

Все аргументы необязательны; любые аргументы поставляются, но полностью игнорируется. Строка создан и возвращен, как если бы выражение (new Date()).toString().

Ответ 2

Я бы не назвал это элегантным, но в моем тестировании (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 ) );

Ответ 3

Вот как вы можете решить конкретный случай: -

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

Ответ 4

Это менее элегантный, но вот решение:

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)

Это действительно все еще... немного уродливо. Но он по крайней мере удобно скрывает беспорядок и не предполагает, что сам скомпилированный код может получить сбор мусора (поскольку это может зависеть от реализации и является вероятной областью для ошибок).

Cheers,  Скотт С. Маккой

Ответ 5

Вот как вы это делаете:

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).

Однако для этого требуется функция. Прокладки, вероятно, будут работать неправильно.

Кстати, один из других ответов предполагает возвращение this из конструктора. Это может затруднить расширение объекта с помощью классического наследования, поэтому я считаю его антипатерном.

Ответ 6

Вы можете сделать это с вопиющим, вопиющим злоупотреблением 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

enjoy =).

Ответ 7

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

Ответ 8

Я знаю, что это было давно, но у меня есть реальный ответ на этот вопрос. Это далеко не невозможно. См. https://gist.github.com/747650 для общего решения.

var F = function(){};
F.prototype = Date.prototype;
var d = new F();
Date.apply(d, comps);

Ответ 9

Вот еще одно решение:

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

Ответ 10

Edited

Извините, я был уверен, что сделал это так много лет назад, сейчас я буду придерживаться:

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

Edit:

Но помните, что дата-объект javascript использует индексы в течение нескольких месяцев, поэтому указанный выше массив означает

8 ноября 2008 00: 16: 34: 254

Ответ 11

var comps = [ 2008, 10, 8, 00, 16, 34, 254 ];
var d = eval("new Date(" + comps.join(",") + ");");