Создание экземпляра объекта 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 52

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

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