web-dev-qa-db-ja.com

prototype.constructor.applyを呼び出してJavaScriptオブジェクトをインスタンス化する

私がやろうとしていることの具体的な例から始めましょう。

年、月、日、時、分、秒、ミリ秒の配列を_[ 2008, 10, 8, 00, 16, 34, 254 ]_の形式で持っています。次の標準コンストラクタを使用して、Dateオブジェクトをインスタンス化したいと思います。

_new Date(year, month, date [, hour, minute, second, millisecond ])
_

配列をこのコンストラクターに渡して、新しいDateインスタンスを取得するにはどうすればよいですか? [pdate:私の質問は、実際にはこの特定の例を超えています。 Date、Array、RegExpなどの組み込みのJavaScriptクラスの一般的な解決策が欲しいのですが、そのコンストラクターは私の手の届かない範囲にあります。 ]

私は次のようなことをやろうとしています:

_var comps = [ 2008, 10, 8, 00, 16, 34, 254 ];
var d = Date.prototype.constructor.apply(this, comps);
_

おそらくどこかに "new"が必要でしょう。上記は単に「_(new Date()).toString()_」を呼び出したかのように現在の時刻を返すだけです。私はまた、上記で完全に間違った方向に進んでいる可能性があることを認めます:)

eval()および配列項目へのアクセスなし一つずつお願いします。配列をそのまま使用できるはずです


更新:さらなる実験

だれもまだ有効な答えを見つけることができなかったので、もっと遊んでみました。これが新しい発見です。

私は自分のクラスでこれを行うことができます:

_function Foo(a, b) {
    this.a = a;
    this.b = b;

    this.toString = function () {
        return this.a + this.b;
    };
}

var foo = new Foo(1, 2);
Foo.prototype.constructor.apply(foo, [4, 8]);
document.write(foo); // Returns 12 -- yay!
_

ただし、組み込みのDateクラスでは機能しません。

_var d = new Date();
Date.prototype.constructor.call(d, 1000);
document.write(d); // Still returns current time :(
_

Numberでも機能しません。

_var n = new Number(42);
Number.prototype.constructor.call(n, 666);
document.write(n); // Returns 42
_

たぶん、これは組み込みオブジェクトでは不可能ですか?私はFirefox BTWでテストしています。

54
Ates Goral

私は自分の調査をさらに進め、Dateクラスの実装方法が原因で、これは不可能な偉業であるという結論に達しました。

SpiderMonkey ソースコードを調べて、Dateの実装方法を確認しました。まとめると、次の数行になります。

_static JSBool
Date(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
    jsdouble *date;
    JSString *str;
    jsdouble d;

    /* Date called as function. */
    if (!(cx->fp->flags & JSFRAME_CONSTRUCTING)) {
        int64 us, ms, us2ms;
        jsdouble msec_time;

        /* NSPR 2.0 docs say 'We do not support PRMJ_NowMS and PRMJ_NowS',
         * so compute ms from PRMJ_Now.
         */
        us = PRMJ_Now();
        JSLL_UI2L(us2ms, PRMJ_USEC_PER_MSEC);
        JSLL_DIV(ms, us, us2ms);
        JSLL_L2D(msec_time, ms);

        return date_format(cx, msec_time, FORMATSPEC_FULL, rval);
    }

    /* Date called as constructor. */
    // ... (from here on it checks the arg count to decide how to create the date)
_

Dateが関数として使用される場合(Date()またはDate.prototype.constructor()のいずれかとまったく同じ)、デフォルトでは現在の時刻がロケール形式の文字列として返されます。これは、渡される引数に関係ありません。

_alert(Date()); // Returns "Thu Oct 09 2008 23:15:54 ..."
alert(typeof Date()); // Returns "string"

alert(Date(42)); // Same thing, "Thu Oct 09 2008 23:15:54 ..."
alert(Date(2008, 10, 10)); // Ditto
alert(Date(null)); // Just doesn't care
_

JSレベルでこれを回避するためにできることはないと思います。そして、これはおそらくこのトピックでの私の追求の終わりです。

私も興味深いことに気づきました:

_    /* Set the value of the Date.prototype date to NaN */
    proto_date = date_constructor(cx, proto);
    if (!proto_date)
        return NULL;
    *proto_date = *cx->runtime->jsNaN;
_

_Date.prototype_は、NaNの内部値を持つDateインスタンスなので、

_alert(Date.prototype); // Always returns "Invalid Date"
                       // on Firefox, Opera, Safari, Chrome
                       // but not Internet Explorer
_

IEは私たちを失望させません。これは少し異なり、おそらく内部値を_-1_に設定して、Date.prototypeが常にEpochの少し前の日付を返すようにします。


更新

私はついにECMA-262自体を掘り下げました。そして、(Dateオブジェクトを使用して)私が達成しようとしていることは、当然のことながら、不可能です。

15.9.2関数として呼び出される日付コンストラクタ

Dateがコンストラクタとしてではなく関数として呼び出されると、現在の時刻(UTC)を表す文字列が返されます。

[〜#〜]注[〜#〜]関数呼び出しDate(…)は、オブジェクト作成式new Date(…)同じ引数を使用します。

15.9.2.1日付([年[、月[、日付[、時間[、分[、秒[、ms]]]]]]])

引数はすべてオプションです。指定された引数はすべて受け入れられますが、完全に無視されます。文字列は、式_(new Date()).toString()_で作成されたかのように返されます。

64
Ates Goral

私はこれをエレガントとは言いませんが、私のテスト(FF3、Saf4、IE8)では機能します。

_var arr = [ 2009, 6, 22, 10, 30, 9 ];_

これの代わりに:

var d = new Date( arr[0], arr[1], arr[2], arr[3], arr[4], arr[5] );

これを試して:

var d = new Date( Date.UTC.apply( window, arr ) + ( (new Date()).getTimezoneOffset() * 60000 ) );

14

これは、特定のケースを解決する方法です。

function writeLn(s)
{
    //your code to write a line to stdout
    WScript.Echo(s)
}

var a =  [ 2008, 10, 8, 00, 16, 34, 254 ]

var d = NewDate.apply(null, a)

function NewDate(year, month, date, hour, minute, second, millisecond)
{
    return new Date(year, month, date, hour, minute, second, millisecond);
}

writeLn(d)

ただし、より一般的な解決策を探しています。コンストラクターメソッドを作成するための推奨コードは、それをreturn this

したがって:-

function Target(x , y) { this.x = x, this.y = y; return this; }

構築することができます:-

var x = Target.apply({}, [1, 2]);

ただし、プロトタイプチェーンが間違っているため、すべての実装がこのように機能するわけではありません。

var n = {};
Target.prototype = n;
var x = Target.apply({}, [1, 2]);
var b = n.isPrototypeOf(x); // returns false
var y = new Target(3, 4);
b = n.isPrototypeOf(y); // returns true
8
AnthonyWJones

エレガントではありませんが、解決策は次のとおりです。

function GeneratedConstructor (methodName, argumentCount) {
    var params = []

    for (var i = 0; i < argumentCount; i++) {
        params.Push("arguments[" + i + "]")
    }

    var code = "return new " + methodName + "(" + params.join(",") +  ")"

    var ctor = new Function(code)

    this.createObject = function (params) {
        return ctor.apply(this, params)
    }
}

これが機能する方法はかなり明白なはずです。コード生成を通じて関数を作成します。この例では、作成するコンストラクターごとにパラメーターの数が固定されていますが、それはとにかく便利です。ほとんどの場合、少なくとも最大数の引数を念頭に置いています。これは、コードを1回生成してから再利用できるため、他のいくつかの例よりも優れています。生成されるコードは、JavaScriptの可変引数機能を利用するため、各パラメーターに名前を付ける必要がなくなります(または、リストにそれらを入力して、生成する関数に引数を渡すことができます)。これが実際の例です:

var dateConstructor = new GeneratedConstructor("Date", 3)
dateConstructor.createObject( [ 1982, 03, 23 ] )

これは以下を返します:

1982年4月23日金00:00:00 GMT-0800(PST)

それは確かにまだ...少し醜いです。しかし、それは少なくとも便宜上混乱を隠し、コンパイルされたコード自体がガベージコレクションを実行できるとは想定していません(実装に依存し、バグが発生する可能性が高いため)。

乾杯、スコット・S・マッコイ

4
Scott S McCoy

これがあなたのやり方です:

function applyToConstructor(constructor, argArray) {
    var args = [null].concat(argArray);
    var factoryFunction = constructor.bind.apply(constructor, args);
    return new factoryFunction();
}

var d = applyToConstructor(Date, [2008, 10, 8, 00, 16, 34, 254]);

これは、ビルトインまたは関数(Dateなど)を兼ねることができるコンストラクターだけでなく、任意のコンストラクターで機能します。

ただし、Ecmascript 5 .bind関数が必要です。シムはおそらく正しく機能しません。

ところで、他の答えの1つは、コンストラクタからthisを返すことを提案しています。そのため、従来の継承を使用してオブジェクトを拡張することが非常に難しくなる可能性があるため、アンチパターンと見なします。

3
kybernetikos

ES6構文では、これを実現するために少なくとも2つの方法があります。

var comps = [ 2008, 10, 8, 00, 16, 34, 254 ];

// with the spread operator
var d1 = new Date(...comps);

// with Reflect.construct
var d2 = Reflect.construct(Date, comps);

console.log('d1:', d1, '\nd2:', d2);
// or more readable:
console.log(`d1: ${d1}\nd2: ${d2}`);
2
Scott Rudiger

ES6スプレッドオペレーターで動作します。あなたは単に:

const arr = [2018, 6, 15, 12, 30, 30, 500];
const date = new Date(...arr);

console.log(date);
1
Wysher

あなたはフラグラントでそれを行うことができますflagrant evalの乱用:

var newwrapper = function (constr, args) {
  var argHolder = {"c": constr};
  for (var i=0; i < args.length; i++) {
    argHolder["$" + i] = args[i];
  }

  var newStr = "new (argHolder['c'])(";
  for (var i=0; i < args.length; i++) {
    newStr += "argHolder['$" + i + "']";
    if (i != args.length - 1) newStr += ", ";
  }
  newStr += ");";

  return eval(newStr);
}

使用例:

function Point(x,y) {
    this.x = x;
    this.y = y;
}
var p = __new(Point, [10, 20]);
alert(p.x); //10
alert(p instanceof Point); //true

=)をお楽しみください。

0
Claudiu
function gettime()
{
    var q = new Date;
    arguments.length && q.setTime( ( arguments.length === 1
        ? typeof arguments[0] === 'number' ? arguments[0] : Date.parse( arguments[0] )
        : Date.UTC.apply( null, arguments ) ) + q.getTimezoneOffset() * 60000 );
    return q;
};

gettime(2003,8,16)

gettime.apply(null,[2003,8,16])
0
ZERONETA