【问题标题】:Instantiating a JavaScript object by calling prototype.constructor.apply通过调用prototype.constructor.apply 实例化一个JavaScript 对象
【发布时间】:2010-09-15 22:21:23
【问题描述】:

让我从一个我正在尝试做的具体示例开始。

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

【问题讨论】:

  • 已经回答我可以看到,事实上你在追求更通用的东西,只有你的主题才清楚。在问题中添加文本,以突出显示不仅针对日期的通用解决方案是明确的。
  • 你不能在“进一步的实验”中的第一个 sn-p 上写 Foo.apply()。我以为 Foo == Foo.prototype.constructor
  • 没什么区别。我得到了同样的结果。
  • 对于您的具体日期示例:new Date(Date.UTC.apply(null,arraywithtime));
  • @MatthewSchinckel 针对特定日期情况的简单解决方法,您可以使用本地时区的日期:Date.fromArray = function (year, month, date, hours, minutes, seconds, ms) { return new Date(year, month, date, hours || 0, minutes || 0, seconds || 0, ms || 0); },然后使用Date.fromArray.apply(null,arraywithtime); 调用它。请注意,您必须至少有年、月和日,否则您将获得无效的日期。

标签: javascript reflection


【解决方案1】:

这就是你可能解决特定情况的方法:-

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

【讨论】:

  • 我已经通过使用类似于您建议的包装器解决了我的特定问题。我仍然想知道的是,如何在内置类(如 Date)的构造函数上使用 apply()。感谢您的详细回复!
【解决方案2】:

编辑

抱歉,我确信我在几年前就做到了,现在我会坚持:

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

编辑:

但请记住,javascript 日期对象使用几个月的索引,所以上面的数组意味着

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

【讨论】:

  • 你测试过这个吗?给我 NaN。
  • 问题是,我怎样才能直接使用数组,而不用单独索引它的项目。
【解决方案3】:
var comps = [ 2008, 10, 8, 00, 16, 34, 254 ];
var d = eval("new Date(" + comps.join(",") + ");");

【讨论】:

  • 我正在寻找不使用 eval() 的解决方案。
【解决方案4】:

由于 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(),它们完全一样),它默认返回当前时间作为 locale 格式的字符串。这与传入的任何参数无关:

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 作为函数调用的日期构造函数

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

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

15.9.2.1 日期([年[,月[,日[,小时[,分[,秒[, 毫秒] ] ] ] ] ])

所有的 参数是可选的;任何论据 提供被接受,但 完全无视。一个字符串是 如同由 表达式(new Date()).toString()

【讨论】:

  • 在存在Function.prototype.bind 的本机实现的情况下,这似乎是可能的:var d = new (Date.bind.apply(Date, [2008, 10, 8, 00, 16, 34, 254])); 将产生一个Date 实例。
  • @CrescentFresh 的解决方案适用于 null:var d = new (Date.bind.apply(Date, [null, 2014, 2, 28, 23, 30])); 返回一个对象:Date {Fri Mar 28 2014 23:30:00 GMT+0100}
【解决方案5】:

它不够优雅,但这里有一个解决方案:

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 (PST)

确实还是……有点丑。但它至少可以方便地隐藏混乱,并且不假设编译后的代码本身可以被垃圾收集(因为这可能取决于实现并且可能是错误的区域)。

干杯, 斯科特·S·麦考伊

【讨论】:

  • new Function(string) 不是另一种评估方式吗?
【解决方案6】:

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

【讨论】:

    【解决方案7】:

    您可以通过公然、公然滥用 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】:

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

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

      【讨论】:

      • 这似乎不适用于 Date 对象:function F() {}; F.prototype = Date.prototype; var d = new F(); Date.apply(d, [ 2008, 10, 8, 00, 16, 34, 254 ]); -> "Tue Apr 05 2011 13:40:43 GMT-0400 (Eastern Daylight Time)"(现在)
      【解决方案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】:

        这就是你的做法:

        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。这会使使用经典继承扩展对象变得非常困难,因此我认为它是一种反模式。

        【讨论】:

          【解决方案11】:
          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])
          

          【讨论】:

            【解决方案12】:

            它将与 ES6 扩展运算符一起使用。 你只需:

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

            【讨论】:

              【解决方案13】:

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

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2018-05-07
                • 2015-11-01
                • 2015-02-07
                • 2021-10-26
                • 1970-01-01
                • 1970-01-01
                • 2011-07-02
                相关资源
                最近更新 更多