如何扩展Javascript Date对象?

时间:2011-05-20 16:56:20

标签: javascript inheritance date node.js

我正在尝试子类化/扩展本机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对象?

12 个答案:

答案 0 :(得分:19)

查看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。

答案 1 :(得分:8)

专门查看Date上的MDC文档:

  

注意:请注意,Date对象只能   通过调用Date或实例化   用它作为构造函数;不像   其他JavaScript对象类型,Date   对象没有文字语法。

似乎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 =一年中的某一天?)

答案 2 :(得分:7)

这可以在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 :(得分:3)

在ES6中,可以子类化内置构造函数(ArrayDateError) - reference

问题是当前的ES5引擎无法做到这一点,因为Babel indicates并且需要一个支持原生ES6的浏览器。

截至今日(2015-04-15),目前用于子类化的ES6 browser support非常弱/不存在。

答案 4 :(得分:2)

EcmaScript规范的第15.9.5节说:

  

在以下对作为Date原型对象属性的函数的描述中,短语'this Date object'指的是调用该函数的该值的对象。除非另有明确说明,否则这些功能都不是通用的;如果此值不是TypeError内部属性的值为[[Class]]的对象,则抛出"Date"异常。此外,短语“this time value”指的是此Date对象表示的时间的Number值,即此Date对象的[[PrimitiveValue]]内部属性的值。

请特别注意“这些函数都不是通用的”这一位,与StringArray不同,这意味着这些方法无法应用于非Date

某些内容是否为Date取决于其[[Class]]是否为"Date"。对于您的子类,[[Class]]"Object"

答案 5 :(得分:1)

我相信Date实际上是一个静态函数,而不是一个真正的对象,因此不能继承使用原型,所以你需要创建一个façade类来包装你需要的任何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;
...

(我不在我可以测试的系统附近,但我希望你能得到这个想法。)

答案 6 :(得分:1)

我几天前尝试过这样做,并认为我可以使用mixins

所以你可以这样做:

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

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

这是添加了缓存的变体,因此它有点复杂。但这个想法是在方法上添加方法。

答案 7 :(得分:1)

您也可以使用github.com/loganfsmyth/babel-plugin-transform-builtin-extend

示例:

import 'babel-polyfill'

export default class MyDate extends Date {
    constructor () {
        super(...arguments)
    }
}

答案 8 :(得分:1)

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 | MDN(第一个注释)上了解Date.call(this, ...arguments)问题。

此解决方案是一种紧凑的解决方法,可在所有支持的浏览器中使用。

答案 9 :(得分:0)

我知道这有点晚了,但是对于可能遇到这个问题的其他人,我有效地将日期替换为我为PhantomJS所需的polyfill的Date。该技术似乎也适用于其他浏览器。还有一些其他问题需要解决,但基本上我采用了与Rudu相同的方法。

完整的评论代码位于https://github.com/kbaltrinic/PhantomJS-DatePolyfill

答案 10 :(得分:0)

根据@sstur的回答

我们可以使用Function.prototype.bind()使用传入的参数动态构造Date对象。

请参阅: 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';
};

<强>验证

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)

答案 11 :(得分:0)

基于@sstur的答案以及@bucabay的改进:

请注意,至少在MDN docs中,这些答案已使用__proto__,因此已弃用,强烈建议不要使用。

幸运的是,可以通过设置类中__proto__中每个单独的函数来执行所需的操作,而无需使用Date.prototype,这可以通过使用Object.getOwnPropertyNames()来简化。

function XDate() {
    var x = new (Function.prototype.bind.apply(Date, [Date].concat(Array.prototype.slice.call(arguments))));

    Object.getOwnPropertyNames(Date.prototype).forEach(function(func) {
        this[func] = function() {
            return x[func].apply(x, Array.prototype.slice.call(arguments));
        };
    }.bind(this));

    this.foo = function() {
        return 'bar';
    };
}

此方法的一个次要缺点是XDate实际上不是Date的子类。支票xdateobj instanceof Datefalse。但这不必担心,因为无论如何您都可以使用Date类的方法。