让我从一个具体的例子开始,说明我正在尝试做的事情。
我有一个[ 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
也许这对于内在对象来说是不可能的?我正在用火狐BTW进行测试。
我自己做了更多的调查,得出的结论是,由于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
是一个内部值为 NaN
的 Date 实例,因此,
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()
.
我很难称之为优雅,但在我的测试(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(
确实还是...有点丑。 但它至少方便地隐藏了混乱,并且不假设编译的代码本身可以被垃圾回收(因为这可能取决于实现并且可能是错误的领域(。
干杯 Scott S. McCoy
这是你的做法:
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 语法,至少有 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}`);
它将与 ES6 扩展运算符一起使用。你只是:
const arr = [2018, 6, 15, 12, 30, 30, 500];
const date = new Date(...arr);
console.log(date);
你可以公然、公然滥用 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]) )
已编辑
对不起,我确定我几年前就这样做了,现在我会坚持:
var d = new Date(comps[0],comps[1],comps[2],comps[3],comps[4],comps[5],comps[6](;
编辑:
但请记住,javascript Date对象使用几个月的索引,所以上面的数组意味着
2008年11月8日 00:16:34:254
var comps = [ 2008, 10, 8, 00, 16, 34, 254 ];
var d = eval("new Date(" + comps.join(",") + ");");