Как расширить объект Javascript Date? - PullRequest
21 голосов
/ 20 мая 2011

Я пытаюсь создать подкласс / расширить собственный объект 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 верхнего уровня?

Ответы [ 11 ]

19 голосов
/ 20 мая 2011

Глядя на код 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 победил 'Позволяет вам подкласс Дата.

8 голосов
/ 20 мая 2011

Проверьте документы MDC по Дата , в частности:

Примечание: обратите внимание, что объекты 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 = день года?)

7 голосов
/ 17 июня 2015

Это можно сделать в 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.

Аналогичный подход можно использовать для создания подклассов Array.

3 голосов
/ 15 апреля 2015

В ES6 будет возможно подразделить встроенные конструкторы (Array, Date и Error) - ссылка

Проблема в том, что нет никакой возможностичтобы сделать это с текущими ядрами ES5, поскольку Babel указывает и потребует браузера с собственной поддержкой ES6.

Текущая поддержка браузера ES6 для подклассов довольно слабая /несуществующий на сегодня (2015-04-15).

2 голосов
/ 20 мая 2011

Раздел 15.9.5 спецификации EcmaScript гласит:

В следующих описаниях функций, являющихся свойствами объекта-прототипа Date, фраза «объект Date» относится к объекту, являющемуся значением this для вызова функции. Если явно не указано иное, ни одна из этих функций не является общей; TypeError исключение выдается, если это значение не является объектом, для которого значение внутреннего свойства [[Class]] равно "Date". Кроме того, фраза «значение этого времени» относится к числовому значению для времени, представленного этим объектом Date, то есть к значению внутреннего свойства [[PrimitiveValue]] этого объекта Date.

Обратите особое внимание на бит, который говорит, что «ни одна из этих функций не является универсальной», что, в отличие от String или Array, означает, что методы нельзя применять к не Date s.

Является ли что-то Date, зависит от того, является ли [[Class]] "Date". Для вашего подкласса [[Class]] - это "Object".

1 голос
/ 28 мая 2017
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 (первое примечание).

Это компактное решение, работающее по назначению во всех поддерживаемых браузерах.

1 голос
/ 08 марта 2016

Вы также можете использовать github.com / loganfsmyth / babel-plugin-transform-builtin-extend

Пример:

import 'babel-polyfill'

export default class MyDate extends Date {
    constructor () {
        super(...arguments)
    }
}
1 голос
/ 08 мая 2015

Я пытался сделать это несколько дней назад и думал, что смогу использовать mixins .

Так что вы можете сделать что-то вроде:

var asSomethingDoable = (function () {
  function doSomething () {
    console.log('Doing something...');
  }
  return function () {
    this.doSomething = doSomething;
    return this;
  }
})();

var date = new Date();
asSomethingDoable.call(date);

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

1 голос
/ 20 мая 2011

Я полагаю, что Date на самом деле является статической функцией, а не истинным объектом, и поэтому ее нельзя унаследовать от использования прототипов, поэтому вам нужно создать класс фасада, чтобы обернуть любую необходимую вам функцию 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;
...

(я не нахожусь рядом с системой, на которой я могу это проверить, но я надеюсь, что вы поняли.)

0 голосов
/ 04 мая 2016

На основе ответа @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)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...