How to extend the Javascript Date object?
Check out the MDC docs on Date specifically:
Note: Note that Date objects can only be instantiated by calling Date or using it as a constructor; unlike other JavaScript object types, Date objects have no literal syntax.
It seems like the Date
object isn't really a JS object at all. When I was writing an extension library, I ended up doing the following:
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");
}
}
At least I did that first. The limitations of the JS Date object in the end got the better of me and I switched to my own data storage approach (eg. why does getDate
=day of year?)
In ES6, it will be possible to subclass built-in constructors (Array
, Date
, and Error
) - reference
Problem is there is no way to do this with current ES5 engines, as Babel indicates and will require a browser with native ES6 support.
The current ES6 browser support for subclassing is pretty weak / non-existant as of today (2015-04-15).
Looking at the v8 code, in date.js:
function DateGetHours() {
var t = DATE_VALUE(this);
if (NUMBER_IS_NAN(t)) return t;
return HOUR_FROM_TIME(LocalTimeNoCheck(t));
}
And looks like DATE_VALUE is a macro that does this:
DATE_VALUE(arg) = (%_ClassOf(arg) === 'Date' ? %_ValueOf(arg) : ThrowDateTypeError());
So, seems like v8 won't let you subclass Date.
This can be done in ES5. It requires modifying the prototype chain directly. This is done using __proto__
or Object.setPrototypeOf()
. I'm using __proto__
in the sample code since that's most widely supported (although the standard is 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';
};
The trick is that we actually instantiate a Date
object (with the correct number of arguments) which gives us an object with it's internal [[Class]]
set correctly. Then we modify it's prototype chain to make it an instance of XDate.
So, we can verify all this by doing:
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)
This is the only way I know of to subclass date because the Date()
constructor does some magic to set the internal [[Class]]
and most date methods require that to be set. This will work in Node, IE 9+ and almost all other JS engines.
Similar approach can be used for subclassing Array.