Подтвердить что ты не робот

Как расширить объект 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 верхнего уровня?

4b9b3361

Ответ 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);

Это вариация с добавленным кешем, поэтому она немного сложнее. Но идея состоит в том, чтобы динамически добавлять методы.

Ответ 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)