Создание объекта JavaScript путем вызова prototype.constructor.apply - PullRequest
53 голосов
/ 08 октября 2008

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

У меня есть массив компонентов год, месяц, день, час, минута, секунда и миллисекунды в виде [ 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 :(

Также не работает с номером:

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

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

Ответы [ 13 ]

64 голосов
/ 20 октября 2008

Я провел собственное расследование и пришел к выводу, что это невозможный подвиг из-за того, как реализован класс Date.

Я проверил исходный код SpiderMonkey , чтобы увидеть, как был реализован Date. Я думаю, что все сводится к следующим нескольким строкам:

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 всегда возвращает дату немного раньше эпохи.


Обновление

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

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

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

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

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

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

14 голосов
/ 23 июля 2009

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

8 голосов
/ 08 октября 2008

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

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 голосов
/ 20 апреля 2009

Это не так элегантно, но вот решение:

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)

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

Ура, Скотт С. Маккой

3 голосов
/ 17 января 2013

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

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 из конструктора. Это может затруднить расширение объекта с использованием классического наследования, поэтому я бы посчитал его антипаттерном.

2 голосов
/ 02 августа 2018

С синтаксисом 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}`);
1 голос
/ 22 марта 2018

Будет работать с оператором распространения ES6. Вы просто:

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

console.log(date);
0 голосов
/ 25 июня 2015
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 голосов
/ 01 августа 2012

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

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 голосов
/ 05 апреля 2011

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

var F = function(){};
F.prototype = Date.prototype;
var d = new F();
Date.apply(d, comps);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...