【问题标题】:How to extend the Javascript Date object?如何扩展 Javascript 日期对象?
【发布时间】:2011-08-29 20:01:26
【问题描述】:

我正在尝试继承/扩展原生 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: this is not a Date object”。

有什么想法吗?还是我坚持扩展顶级 Date 对象?

【问题讨论】:

    标签: javascript inheritance date node.js


    【解决方案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。

    【讨论】:

    • 尽管 v8 中有 %_ClassOf 的要求,但实际上有一个将 Date 子类化的解决方法。见my answer here
    • @sstur 有趣,但 __proto__ 已弃用
    【解决方案2】:

    这可以在 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。

    【讨论】:

    • 真正有趣的方法。您可以使用 Date.bind() 消除 switch 语句。例如:var x = new (Function.prototype.bind.apply(Date, [Date].concat(Array.prototype.slice.call(arguments))))
    • @bucabay,但是,它似乎比在 Windows 8.1 上切换 Chrome 47 慢 10 倍
    • @Kanstantsin 可能是因为prototype.bind() 在chrome中很慢。 stackoverflow.com/questions/18895305/… 慢 10 倍不会产生现实世界的优化差异,因为函数在 O(n) 中执行,其中 n
    • @bucabay 这是一个很好的建议:bind.apply(Date, [Date].concat(slice.call(arguments))) 我认为慢 10 倍是合理的,因为有机会如此出色地推广此功能。我怀疑我们甚至可以进行进一步的优化以减轻性能影响。比如合并concat + slice,预绑定一些东西等
    • @bucabay:旁注:原来任何值都可以作为第一个数组元素提供:[Date].concat(...) 可以是 [null].concat(...)(尽管我不确定它是否重要)
    【解决方案3】:

    具体查看Date 上的 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=一年中的某天?)

    【讨论】:

      【解决方案4】:

      在 ES6 中,可以继承内置构造函数(ArrayDateError) - reference

      问题是目前的 ES5 引擎无法做到这一点,如 Babel indicates,并且需要具有原生 ES6 支持的浏览器。

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

      【讨论】:

        【解决方案5】:

        EcmaScript 规范的第 15.9.5 节说:

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

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

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

        【讨论】:

          【解决方案6】:

          我相信 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;
          ...
          

          (我没有可以在上面测试的系统,但我希望你能明白。)

          【讨论】:

            【解决方案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】:

                基于@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)
                

                【讨论】:

                  【解决方案10】:
                  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) 问题的信息(第一个注释)。

                  此解决方案是一种紧凑的解决方法,可在所有支持的浏览器中按预期工作。

                  【讨论】:

                    【解决方案11】:

                    我知道这有点晚了,但是对于可能遇到此问题的其他人,我设法有效地将 Date 子类化为 PhantomJS 所需的 polyfill。该技术似乎也适用于其他浏览器。还有一些额外的问题需要解决,但基本上我遵循了与 Rudu 相同的方法。

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

                    【讨论】:

                      【解决方案12】:

                      基于@sstur 的回答和@bucabay 的改进:

                      请注意,__proto__ 在这些答案中被使用,这是不推荐的,并且强烈建议不要使用它,至少根据MDN docs

                      幸运的是,通过在我们的类中设置 Date.prototype 中的每个单独的函数,可以在不使用 __proto__ 的情况下完成所需的操作,通过使用 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 类的方法。

                      【讨论】:

                        猜你喜欢
                        • 1970-01-01
                        • 1970-01-01
                        • 2013-08-03
                        • 1970-01-01
                        • 2017-11-21
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2011-05-29
                        相关资源
                        最近更新 更多