I\'m trying to subclass/extend the native Date object, without modifying the native object itself.
I\'ve tried this:
var util = require(\'util\')
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.
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?)
I've tried to do this some days ago and thought I could use mixins.
So you could do something like:
var asSomethingDoable = (function () {
function doSomething () {
console.log('Doing something...');
}
return function () {
this.doSomething = doSomething;
return this;
}
})();
var date = new Date();
asSomethingDoable.call(date);
This is the variation with cache added, so it's a little bit more complicated. But the idea is to add the methods dinamically.
Section 15.9.5 of the EcmaScript spec says:
In following descriptions of functions that are properties of the Date prototype object, the phrase 'this Date object' refers to the object that is the this value for the invocation of the function. Unless explicitly noted otherwise, none of these functions are generic; a
TypeError
exception is thrown if the this value is not an object for which the value of the[[Class]]
internal property is"Date"
. Also, the phrase 'this time value' refers to the Number value for the time represented by this Date object, that is, the value of the[[PrimitiveValue]]
internal property of this Date object.
Note specifically the bit that says "none of these functions are generic" which, unlike for String
or Array
, means that the methods cannot be applied to non-Date
s.
Whether something is a Date
depends on whether its [[Class]]
is "Date"
. For your subclass the [[Class]]
is "Object"
.
Based on answer by @sstur
We can use Function.prototype.bind()
to construct the Date object dynamically with the passed in arguments.
See: Mozilla Developer Network: bind() method
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';
};
Verification:
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)