Как расширить объект Javascript Date?
Я пытаюсь подкласс/продлить родной объект 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 верхнего уровня?
Ответы
Ответ 1
Глядя на код 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 - это макрос, который делает это:
DATE_VALUE(arg) = (%_ClassOf(arg) === 'Date' ? %_ValueOf(arg) : ThrowDateTypeError());
Итак, похоже, что v8 не даст вам подкласса Date.
Ответ 2
Ознакомьтесь с документами MDC на Date:
Примечание. Обратите внимание, что объекты Date могут быть вызван путем вызова даты или используя его как конструктор; В отличие от другие типы объектов JavaScript, дата объекты не имеют буквенного синтаксиса.
Кажется, что объект 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");
}
}
По крайней мере, я сделал это первым. Ограничения объекта JS Date в конце концов улучшились, и я переключился на свой собственный подход к хранению данных (например, почему getDate
= день года?)
Ответ 3
Это можно сделать в ES5. Это требует непосредственной модификации цепи прототипа. Это делается с помощью __proto__
или Object.setPrototypeOf()
. Я использую __proto__
в примере кода, так как это наиболее широко поддерживается (хотя стандарт 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.
Итак, мы можем проверить все это, выполнив:
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()
делает магию для установки внутреннего [[Class]]
, и большинство методов даты требуют, чтобы они были установлены. Это будет работать в Node, IE 9+ и почти во всех других JS-машинах.
Аналогичный подход может быть использован для подклассификации массива.
Ответ 4
В ES6 возможно подклассы встроенных конструкторов (Array
, Date
и Error
) - ссылка
Проблема в том, что с нынешними двигателями ES5 не существует способа, так как Babel указывает, и для этого потребуется браузер с поддержкой родной ES6.
Текущая ES6 поддержка браузера для подкласса довольно слабая/несуществующая на сегодняшний день (2015-04-15).
Ответ 5
В разделе 15.9.5 спецификации EcmaScript говорится:
В следующих описаниях функций, являющихся свойствами объекта прототипа Date, фраза "этот объект Date" относится к объекту, который является этим значением для вызова функции. Если не указано иное, ни одна из этих функций не является общей; Исключение TypeError
генерируется, если это значение не является объектом, для которого значение внутреннего свойства [[Class]]
равно "Date"
. Кроме того, фраза "это значение времени" относится к значению Number для времени, представленного этим объектом Date, то есть значением внутреннего свойства [[PrimitiveValue]]
этого объекта Date.
Обратите внимание, что бит, который говорит, что "ни одна из этих функций не является общей", которая, в отличие от String
или Array
, означает, что методы не могут применяться к не Date
s.
Является ли что-то Date
, зависит от того, является ли его [[Class]]
"Date"
. Для вашего подкласса [[Class]]
есть "Object"
.
Ответ 6
Я считаю, что дата - это статическая функция, а не истинный объект и, как таковая, не может быть унаследована от использования прототипов, так что вам нужно будет создать класс фасад, чтобы обернуть любую функциональность 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;
...
(Я не рядом с системой, на которой я могу проверить это, но надеюсь, что вы поняли эту идею.)
Ответ 7
Я пытался это сделать несколько дней назад и думал, что могу использовать mixins.
Итак, вы можете сделать что-то вроде:
var asSomethingDoable = (function () {
function doSomething () {
console.log('Doing something...');
}
return function () {
this.doSomething = doSomething;
return this;
}
})();
var date = new Date();
asSomethingDoable.call(date);
Это вариация с добавленным кешем, поэтому она немного сложнее. Но идея состоит в том, чтобы динамически добавлять методы.
Ответ 8
Вы также можете использовать github.com/loganfsmyth/babel-plugin-transform-builtin-extend
Пример:
import 'babel-polyfill'
export default class MyDate extends Date {
constructor () {
super(...arguments)
}
}
Ответ 9
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
Проблема с конструкторской функцией Date
уже объясняется в других ответах. Вы можете прочитать о проблеме Date.call(this, ...arguments)
на Дата | MDN (первое примечание).
Это решение представляет собой компактное обходное решение, которое работает по назначению во всех поддерживающих браузерах.
Ответ 10
Я знаю, что это немного поздно, но для других, кто может столкнуться с этой проблемой, я создал эффективный подкласс Date для polyfill, который мне нужен для PhantomJS. Этот метод, похоже, работает и в другом браузере. Было несколько дополнительных проблем для разработки, но по существу я придерживался того же подхода, что и Руду.
Полный комментируемый код находится в https://github.com/kbaltrinic/PhantomJS-DatePolyfill.
Ответ 11
Основываясь на ответе @sstur
Мы можем использовать Function.prototype.bind()
для динамического создания объекта Date с переданными аргументами.
Смотрите: Сеть разработчиков Mozilla: метод 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';
};
Проверка:
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)