sumar - ¿Cómo extender el objeto fecha de Javascript?
string to date javascript (11)
Estoy intentando subclasificar / extender el objeto Fecha nativo, sin modificar el objeto nativo en sí.
He intentado esto:
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());
y esto:
function MyDate() {
}
MyDate.prototype = new Date();
MyDate.prototype.doSomething = function() {
console.log("DO");
}
var date = new MyDate();
date.doSomething();
console.log(date);
En ambos casos, el date.doSomething()
funciona, pero cuando llamo a alguno de los métodos nativos como date.getHours()
o incluso console.log(date)
, aparece ''TypeError: este no es un objeto Date''.
¿Algunas ideas? ¿O me limito a extender el objeto Fecha de nivel superior?
Basado en la respuesta de @sstur.
Podemos usar Function.prototype.bind()
para construir el objeto Date dinámicamente con los argumentos pasados.
Ver: Mozilla Developer Network: método 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'';
};
Verificación:
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)
Creo que Date es en realidad una función estática, no un objeto verdadero, y como tal no se puede heredar del uso de prototipos, por lo que deberá crear una clase de fachada para envolver cualquier funcionalidad de fecha que necesite.
Intentaría construir tu nuevo objeto de fecha como:
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;
...
(No estoy cerca de un sistema en el que pueda probar esto, pero espero que se haga una idea).
Echa un vistazo a los documentos de MDC en Date específicamente:
Nota: Tenga en cuenta que los objetos Date solo pueden crearse instancias llamando a Date o usándolos como constructor; a diferencia de otros tipos de objetos de JavaScript, los objetos de fecha no tienen sintaxis literal.
Parece que el objeto Date
no es realmente un objeto JS en absoluto. Cuando estaba escribiendo una biblioteca de extensión, terminé haciendo lo siguiente:
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");
}
}
Al menos yo hice eso primero. Las limitaciones del objeto JS Date al final me getDate
y getDate
a mi propio método de almacenamiento de datos (por ejemplo, ¿por qué getDate
= día del año?)
En ES6, será posible subclasificar constructores integrados ( Array
, Date
y Error
) - reference
El problema es que no hay manera de hacer esto con los motores ES5 actuales, como indicates Babel y requerirá un navegador con soporte nativo de ES6.
El soporte actual del navegador ES6 para subclases es bastante débil / inexistente a partir de hoy (2015-04-15).
Esto se puede hacer en ES5. Requiere modificar la cadena del prototipo directamente. Esto se hace usando __proto__
o Object.setPrototypeOf()
. Estoy usando __proto__
en el código de muestra, ya que es el más ampliamente compatible (aunque el estándar es 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'';
};
El truco es que realmente creamos una instancia de un objeto Date
(con el número correcto de argumentos) que nos da un objeto con su [[Class]]
interno configurado correctamente. Luego modificamos su cadena de prototipos para que sea una instancia de XDate.
Entonces, podemos verificar todo esto haciendo:
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)
Esta es la única forma que conozco de subclase de fecha porque el constructor Date()
hace algo de magia para establecer la [[Class]]
interna y la mayoría de los métodos de fecha requieren que se establezca. Esto funcionará en Node, IE 9+ y en casi todos los otros motores JS.
Se puede usar un enfoque similar para la subclasificación de Array.
Intenté hacer esto hace algunos días y pensé que podía usar mixins .
Así que podrías hacer algo como:
var asSomethingDoable = (function () {
function doSomething () {
console.log(''Doing something...'');
}
return function () {
this.doSomething = doSomething;
return this;
}
})();
var date = new Date();
asSomethingDoable.call(date);
Esta es la variación con el caché agregado, por lo que es un poco más complicado. Pero la idea es sumar dinámicamente los métodos.
La sección 15.9.5 de la especificación EcmaScript dice:
En las siguientes descripciones de funciones que son propiedades del objeto Prototipo de fecha, la frase "este objeto Fecha" se refiere al objeto que tiene este valor para la invocación de la función. A menos que se indique explícitamente lo contrario, ninguna de estas funciones es genérica; se lanza una excepción
TypeError
si este valor no es un objeto para el cual el valor de la propiedad interna[[Class]]
es"Date"
. Además, la frase ''este valor de tiempo'' se refiere al valor de Número para el tiempo representado por este objeto Fecha, es decir, el valor de la propiedad interna[[PrimitiveValue]]
de este objeto Fecha.
Tenga en cuenta específicamente el bit que dice "ninguna de estas funciones es genérica", lo que, a diferencia de String
o Array
, significa que los métodos no se pueden aplicar a no- Date
s.
Si algo es una Date
depende de si su [[Class]]
es "Date"
. Para su subclase, la [[Class]]
es "Object"
.
Mirando el código v8, en date.js:
function DateGetHours() {
var t = DATE_VALUE(this);
if (NUMBER_IS_NAN(t)) return t;
return HOUR_FROM_TIME(LocalTimeNoCheck(t));
}
Y parece que DATE_VALUE es una macro que hace esto:
DATE_VALUE(arg) = (%_ClassOf(arg) === ''Date'' ? %_ValueOf(arg) : ThrowDateTypeError());
Entonces, parece que v8 no te dejará subclase de fecha.
Sé que esto es un poco tarde, pero para otras personas que pueden encontrar este problema, me las arreglé para subclasificar efectivamente la Fecha de un polyfill que necesitaba para PhantomJS. La técnica parece funcionar también en otro navegador. Hubo algunos problemas adicionales por resolver, pero esencialmente seguí el mismo enfoque que Rudu.
El código completo comentado está en https://github.com/kbaltrinic/PhantomJS-DatePolyfill .
También puede usar github.com/loganfsmyth/babel-plugin-transform-builtin-extend
Ejemplo:
import ''babel-polyfill''
export default class MyDate extends Date {
constructor () {
super(...arguments)
}
}
var SubDate = function() {
var dateInst = new Date(...arguments); // spread arguments object
/* Object.getPrototypeOf(dateInst) === Date.prototype */
Object.setPrototypeOf(dateInst, SubDate.prototype); // redirectionA
return dateInst; // now instanceof SubDate
};
Object.setPrototypeOf(SubDate.prototype, Date.prototype); // redirectionB
// do something useful
Object.defineProperty(SubDate.prototype, ''year'', {
get: function() {return this.getFullYear();},
set: function(y) {this.setFullYear(y);}
});
var subDate = new SubDate();
subDate.year; // now
subDate.year = 2050; subDate.getFullYear(); // 2050
El problema con la función de construcción de Date
ya se explica en las otras respuestas. Puede leer sobre el problema Date.call(this, ...arguments)
en Date | MDN (primera nota).
Esta solución es una solución compacta que funciona según lo previsto en todos los navegadores compatibles.