ネイティブオブジェクト自体を変更せずに、ネイティブの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オブジェクトを拡張することに固執していますか?
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をサブクラス化できないようです。
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?)
これは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エンジンで機能します。
同様のアプローチを配列のサブクラス化に使用できます。
EcmaScript仕様のセクション15.9.5には、次のように書かれています。
以下のDateプロトタイプオブジェクトのプロパティである関数の説明では、「this Dateオブジェクト」という句は、関数の呼び出しのthis値であるオブジェクトを指します。特に明記されていない限り、これらの関数はいずれも汎用ではありません。この値が
[[Class]]
内部プロパティの値が"Date"
であるオブジェクトでない場合、TypeError
例外がスローされます。また、「this time value」という句は、このDateオブジェクトによって表される時間のNumber値、つまり、このDateオブジェクトの[[PrimitiveValue]]
内部プロパティの値を指します。
特に、「これらの関数はいずれも汎用ではない」というビットに注意してください。これは、String
またはArray
とは異なり、メソッドを非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);
これはキャッシュが追加されたバリエーションであるため、もう少し複雑です。しかし、アイデアは、メソッドを動的に追加することです。
@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)
github.com/loganfsmyth/babel-plugin-transform-builtin-extend を使用することもできます
例:
import 'babel-polyfill'
export default class MyDate extends Date {
constructor () {
super(...arguments)
}
}
_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)
問題について読むことができます。
このソリューションは、サポートされているすべてのブラウザーで意図したとおりに機能するコンパクトな回避策です。
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;
...
(私はこれをテストできるシステムの近くにいませんが、あなたがアイデアを得ることを願っています。)
@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クラスのメソッドを使用できるので、これは心配する必要はありません。
これは少し遅いことはわかっていますが、この問題が発生する可能性がある他の人のために、PhantomJSに必要なポリフィルのDateを効果的にサブクラス化するように管理しました。この手法は他のブラウザでも機能するようです。解決すべき追加の問題がいくつかありましたが、基本的に私はRuduと同じアプローチに従いました。
コメント付きの完全なコードは https://github.com/kbaltrinic/PhantomJS-DatePolyfill にあります。