簡體   English   中英

如何擴展 Javascript Date 對象?

[英]How to extend the Javascript Date object?

我正在嘗試子類化/擴展本機 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 對象?

查看 date.js 中的 v8 代碼:

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 子類化。

這可以在 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 進行子類化的唯一方法,因為Date()構造函數做了一些魔法來設置內部[[Class]]並且大多數日期方法都需要設置它。 這將適用於 Node、IE 9+ 和幾乎所有其他 JS 引擎。

類似的方法可用於子類化 Array。

具體查看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 = day of year?)

在 ES6 中,可以對內置構造函數( ArrayDateError )進行子類化 - 參考

問題是目前的 ES5 引擎無法做到這一點,正如 Babel指出的那樣,並且需要具有原生 ES6 支持的瀏覽器。

截至今天(2015-04-15),當前的 ES6瀏覽器對子類化的支持非常弱/不存在。

EcmaScript 規范的第 15.9.5 節說:

在作為 Date 原型對象屬性的函數的以下描述中,短語“this Date object”是指作為函數調用的 this 值的對象。 除非另有明確說明,否則這些函數都不是通用的; 如果 this 值不是[[Class]]內部屬性的值為"Date"的對象,則會引發TypeError異常。 此外,短語“此時間值”是指此 Date 對象表示的時間的 Number 值,即此 Date 對象的[[PrimitiveValue]]內部屬性的值。

請特別注意“這些函數中沒有一個是通用的”,這與StringArray ,這意味着這些方法不能應用於非Date s。

某個東西是否是Date取決於它的[[Class]]是否是"Date" 對於您的子類, [[Class]]"Object"

幾天前我嘗試過這樣做,並認為我可以使用mixins

所以你可以這樣做:

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

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

這是添加緩存后的變化,因此稍微復雜一些。 但我們的想法是動態地添加方法。

你也可以使用github.com/loganfsmyth/babel-plugin-transform-b​​uiltin-extend

例子:

import 'babel-polyfill'

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

基於@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)
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 |上閱讀Date.call(this, ...arguments)問題。 MDN (第一注)。

此解決方案是一種緊湊的解決方法,可在所有支持的瀏覽器中按預期工作。

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

(我不在一個可以測試的系統附近,但我希望你能明白。)

我知道這有點晚了,但是對於可能遇到此問題的其他人,我設法為 PhantomJS 所需的 polyfill 有效地將 Date 子類化。 該技術似乎也適用於其他瀏覽器。 還有一些額外的問題需要解決,但基本上我遵循了與 Rudu 相同的方法。

完整的注釋代碼位於https://github.com/kbaltrinic/PhantomJS-DatePolyfill

基於@sstur 的回答和@bucabay 的改進:

請注意, __proto__正在這些答案中使用,它已被棄用,並且強烈建議不要使用它,至少根據MDN 文檔

幸運的是,通過在我們的類中從Date.prototype設置每個單獨的函數, 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 類的方法。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM