如何扩展Javascriptdate对象?

我试图subclass /扩展本地的date对象,而不修改本地对象本身。

我试过这个:

var util = require('util'); function MyDate() { Date.call(this); } util.inherits(MyDate, Date); MyDate.prototype.doSomething = function() { console.log('Doing something...'); }; var date = new MyDate(); date.doSomething(); console.log(date); console.log(date.getHours()); 

和这个:

 function MyDate() { } MyDate.prototype = new Date(); MyDate.prototype.doSomething = function() { console.log("DO"); } var date = new MyDate(); date.doSomething(); console.log(date); 

在这两种情况下, date.doSomething()起作用,但是当我调用任何本地方法(如date.getHours()console.log(date) ,我得到'TypeError:这不是Date对象。

有任何想法吗? 或者我坚持扩展顶级的date对象?

查看v8代码,在date.js中:

 function DateGetHours() { var t = DATE_VALUE(this); if (NUMBER_IS_NAN(t)) return t; return HOUR_FROM_TIME(LocalTimeNoCheck(t)); } 

看起来像DATE_VALUE是这样做的macros:

 DATE_VALUE(arg) = (%_ClassOf(arg) === 'Date' ? %_ValueOf(arg) : ThrowDateTypeError()); 

所以,好像v8不会让你子类Date。

详细查看Date上的MDC文档:

注意:Date对象只能通过调用Date或作为构造函数来实例化; 与其他JavaScript对象types不同,Date对象没有文字语法。

看来Date对象根本就不是一个JS对象。 当我写一个扩展库时,我做了以下工作:

 function MyDate() { var _d=new Date(); function init(that) { var i; var which=['getDate','getDay','getFullYear','getHours',/*...*/,'toString']; for (i=0;i<which.length;i++) { that[which[i]]=_d[which[i]]; } } init(this); this.doSomething=function() { console.log("DO"); } } 

至less我是这样做的。 JS Date对象的局限性最终得到了我的更好的回报,我转向了自己的数据存储方法(例如,为什么getDate =每年的一天?)

这可以在ES5中完成。 它需要直接修改原型链。 这是使用__proto__Object.setPrototypeOf() 。 我在示例代码中使用__proto__ ,因为它被广泛支持(尽pipe标准是Object.setPrototypeOf )。

 function XDate(a, b, c, d, e, f, g) { var x; switch (arguments.length) { case 0: x = new Date(); break; case 1: x = new Date(a); break; case 2: x = new Date(a, b); break; case 3: x = new Date(a, b, c); break; case 4: x = new Date(a, b, c, d); break; case 5: x = new Date(a, b, c, d, e); break; case 6: x = new Date(a, b, c, d, e, f); break; default: x = new Date(a, b, c, d, e, f, g); } x.__proto__ = XDate.prototype; return x; } XDate.prototype.__proto__ = Date.prototype; XDate.prototype.foo = function() { return 'bar'; }; 

诀窍是我们实际上实例化了一个Date对象(具有正确数量的参数),它给了我们一个正确设置了内部[[Class]]的对象。 然后我们修改它的原型链,使其成为XDate的一个实例。

所以,我们可以通过这样做来validation所有这些:

 var date = new XDate(2015, 5, 18) console.log(date instanceof Date) //true console.log(date instanceof XDate) //true console.log(Object.prototype.toString.call(date)) //[object Date] console.log(date.foo()) //bar console.log('' + date) //Thu Jun 18 2015 00:00:00 GMT-0700 (PDT) 

这是我知道子类date的唯一方法,因为Date()构造函数有一些魔法来设置内部[[Class]] ,大多数date方法需要设置。 这将工作在节点,IE 9 +和几乎所有其他的JS引擎。

类似的方法可以用于子类化Array。

在ES6中,可以对内置的构造函数( ArrayDateError )进行子类化 – 引用

问题是,目前的ES5引擎没有办法做到这一点,因为Babel 指出并需要一个支持原生ES6的浏览器。

目前ES6 浏览器对子类化的支持是相当薄弱/不存在的,截至今天(2015-04-15)。

EcmaScript规范的15.9.5节说:

在以下对作为Date原型对象的属性的函数的描述中,短语“此Date对象”指的是作为调用该函数的该值的对象。 除非另外明确指出,否则这些function都不是通用的; 如果此值不是[[Class]]内部属性值为"Date"的对象,则会引发TypeErrorexception。 另外,短语“这个时间值”是指由这个Date对象表示的时间的Number值,也就是这个Date对象的[[PrimitiveValue]]内部属性的值。

特别要注意的是,表示“这些函数都不是通用的”,它与StringArray ,意味着这些方法不能应用于非Date

是否是Date取决于其[[Class]]是否为"Date" 。 对于你的子类, [[Class]]"Object"

我相信date实际上是一个静态函数,而不是一个真正的对象,因此不能从原型inheritance,所以你需要创build一个façade类来包装你需要的任何datefunction。

我会尝试构build新的date对象为:

 function MyDate(value) { this.value=new Date(value); // add operations that operate on this.value this.prototype.addDays=function(num){ ... }; this.prototype.toString=function() { return value.toString(); }; } // add static methods from Date MyDate.now=Date.now; MyDate.getTime=Date.getTime; ... 

(我不是靠近一个系统,我可以testing这个,但我希望你明白了。)

前几天我试过这样做,并认为我可以使用mixin 。

所以你可以做这样的事情:

 var asSomethingDoable = (function () { function doSomething () { console.log('Doing something...'); } return function () { this.doSomething = doSomething; return this; } })(); var date = new Date(); asSomethingDoable.call(date); 

这是添加了caching的变化,所以它有点复杂。 但是,这个想法是添加方法dinamically。

您也可以使用github.com/loganfsmyth/babel-plugin-transform-b​​uiltin-extend

例:

 import 'babel-polyfill' export default class MyDate extends Date { constructor () { super(...arguments) } } 
 var DateExtension = function() { var instance = new Date(...arguments); // spread arguments object Object.setPrototypeOf(instance, DateExtension.prototype); return instance; // instance of DateExtension }; Object.setPrototypeOf(DateExtension.prototype, Date.prototype); // do something useful // accessor descriptor: if omitted, configurable and enumerable are false Object.defineProperty(DateExtension.prototype, 'year', { get: function() {return this.getFullYear();}, set: function(y) {this.setFullYear(y);} }); var extDate = new DateExtension(); // new operator: extDate = instance extDate.year; // now extDate.year = 2050; extDate.getFullYear(); // 2050 

其他答案已经解释了Date构造函数的问题。 你可以阅读关于Date |的Date.call(this, ...arguments)问题 MDN (第一注)。

答案是一个紧凑的解决方法,可以在所有支持的浏览器中按预期工作。

我知道这有点晚了,但对于其他可能遇到此问题的人,我pipe理着有效地将Date的子类用于PhantomJS所需的polyfill。 该技术似乎也适用于其他浏览器。 还有一些额外的问题需要解决,但基本上我遵循了与Rudu相同的方法。

完整的评论代码是在https://github.com/kbaltrinic/PhantomJS-DatePolyfill

基于@sstur的回答

我们可以使用Function.prototype.bind()来dynamic地用传入的参数构造Date对象。

请参阅: Mozilla开发者networking:bind()方法

 function XDate() { var x = new (Function.prototype.bind.apply(Date, [Date].concat(Array.prototype.slice.call(arguments)))) x.__proto__ = XDate.prototype; return x; } XDate.prototype.__proto__ = Date.prototype; XDate.prototype.foo = function() { return 'bar'; }; 

validation:

 var date = new XDate(2015, 5, 18) console.log(date instanceof Date) //true console.log(date instanceof XDate) //true console.log(Object.prototype.toString.call(date)) //[object Date] console.log(date.foo()) //bar console.log('' + date) // Thu Jun 18 2015 00:00:00 GMT-0500 (CDT)