通过调用prototype.constructor.apply来实例化JavaScript对象

时间:2008-10-08 04:32:36

标签: javascript reflection

让我先从我正在尝试做的具体例子开始。

我有[ 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进行测试。

13 个答案:

答案 0 :(得分:64)

我对自己做了更多的调查,并得出结论这是一个不可能的壮举,因为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始终在epoch之前稍微返回一个日期。


更新

我终于挖到了ECMA-262本身,事实证明,我想要实现的目标(使用Date对象) - 根据定义 - 是不可能的:

  

15。9。2日期构造函数被称为函数

     

当Date被称为a时   函数而不是构造函数,   它返回一个表示的字符串   当前时间(UTC)。

     

注意该功能   呼叫Date(…)不等同于   对象创建表达式new Date(…)   用相同的论点。

     

15。9。2。1日期([年[,月份[,日期[,小时[,分钟[,秒] [,   ms]]]]]]])

     

所有的   参数是可选的;任何争论   供应是接受但是   完全被忽略了字符串是   创建并返回,好像是由   表达(new Date()).toString()

答案 1 :(得分:14)

我很难称之为优雅,但在我的测试中(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 ) );

答案 2 :(得分:8)

这是您解决具体案例的方法: -

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

答案 3 :(得分: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 ] )

这将返回以下内容:

  

1982年4月23日星期五00:00:00 GMT-0800(太平洋标准时间)

确实还是......有点难看。但它至少可以方便地隐藏乱七八糟的东西并且不会认为编译的代码本身可以收集垃圾(因为这可能取决于实现并且可能是错误的区域)。

干杯,    Scott S. McCoy

答案 4 :(得分:3)

您就是这样做的:

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。这可能会使用经典继承扩展对象非常困难,所以我认为它是反模式。

答案 5 :(得分:2)

使用ES6语法,至少有两种方法可以实现此目的:

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}`);

答案 6 :(得分:1)

它将与ES6传播运营商合作。 你只是:

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

console.log(date);

答案 7 :(得分:0)

你可以公然,公然滥用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

享受=)。

答案 8 :(得分:0)

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

答案 9 :(得分:-1)

我知道这已经很久了,但我对这个问题有了真正的答案。这绝非不可能。有关通用解决方案,请参阅https://gist.github.com/747650

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

答案 10 :(得分:-1)

这是另一种解决方案:

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

答案 11 :(得分:-2)

被修改

对不起,我确信几年前我就这么做了,现在我会坚持:

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

编辑:

但请记住,javascript Date-object使用索引数月,所以上面的数组意味着

2008年11月8日00:16:34:254

答案 12 :(得分:-3)

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