web-dev-qa-db-ja.com

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オブジェクトを拡張することに固執していますか?

23
evilcelery

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をサブクラス化できないようです。

19
Geoff Chappell

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");
   }
}

少なくとも私は最初にそれをしました。最終的にJSDateオブジェクトの制限が改善され、独自のデータストレージアプローチに切り替えました(たとえば、getDate = day of year?)

8
Rudu

これは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]]_を設定するために魔法をかけ、ほとんどのdateメソッドでは設定が必要なため、これが日付をサブクラス化するための唯一の方法です。これは、Node、IE 9+、および他のほとんどすべてのJSエンジンで機能します。

同様のアプローチを配列のサブクラス化に使用できます。

7
sstur

ES6では、組み込みコンストラクター(ArrayDate、およびError)をサブクラス化することが可能になります- 参照

問題は、Babel indicates であり、ネイティブES6をサポートするブラウザーが必要になるため、現在のES5エンジンでこれを行う方法がないことです。

サブクラス化のための現在のES6 ブラウザサポート はかなり弱い/今日(2015-04-15)の時点では存在しません。

3
Sean Lynch

EcmaScript仕様のセクション15.9.5には、次のように書かれています。

以下のDateプロトタイプオブジェクトのプロパティである関数の説明では、「this Dateオブジェクト」という句は、関数の呼び出しのthis値であるオブジェクトを指します。特に明記されていない限り、これらの関数はいずれも汎用ではありません。この値が[[Class]]内部プロパティの値が"Date"であるオブジェクトでない場合、TypeError例外がスローされます。また、「this time value」という句は、このDateオブジェクトによって表される時間のNumber値、つまり、このDateオブジェクトの[[PrimitiveValue]]内部プロパティの値を指します。

特に、「これらの関数はいずれも汎用ではない」というビットに注意してください。これは、StringまたはArrayとは異なり、メソッドを非Datesに適用できないことを意味します。

何かがDateであるかどうかは、その[[Class]]"Date"であるかどうかによって異なります。サブクラスの場合、[[Class]]"Object"です。

2
Mike Samuel

私は数日前にこれを試みましたが、 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
Marcos Garcia

@ssturによる回答に基づく

Function.prototype.bind()を使用して、渡された引数を使用してDateオブジェクトを動的に構築できます。

参照:Mozilla Developer Network: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)
1
bucabay

github.com/loganfsmyth/babel-plugin-transform-b​​uiltin-extend を使用することもできます

例:

import 'babel-polyfill'

export default class MyDate extends Date {
    constructor () {
        super(...arguments)
    }
}
1
adius
_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)問題について読むことができます。

このソリューションは、サポートされているすべてのブラウザーで意図したとおりに機能するコンパクトな回避策です。

1
Neni

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;
...

(私はこれをテストできるシステムの近くにいませんが、あなたがアイデアを得ることを願っています。)

1
Rob Raisch

@ssturによる回答と@bucabayによるその改善に基づく:

___proto___はこれらの回答で使用されていることに注意してください。これは非推奨であり、少なくとも MDN docs によれば、その使用は強く推奨されていません。

幸い、クラスの___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 Date_はfalseです。しかし、とにかくDateクラスのメソッドを使用できるので、これは心配する必要はありません。

0
Siddharth

これは少し遅いことはわかっていますが、この問題が発生する可能性がある他の人のために、PhantomJSに必要なポリフィルのDateを効果的にサブクラス化するように管理しました。この手法は他のブラウザでも機能するようです。解決すべき追加の問題がいくつかありましたが、基本的に私はRuduと同じアプローチに従いました。

コメント付きの完全なコードは https://github.com/kbaltrinic/PhantomJS-DatePolyfill にあります。

0