applyメソッドとnewを使ってインスタンスを生成するコードの例 - Qiita へのコメントです。長くなったので記事にしました。
var a = new (function () { return Array.apply(this, [1,2,3]); })();
これ、一見うまくいってるように見えますが、間違ってますね。
function Hoge (a) {
this.x = a;
}
var a = new (function () { return Hoge.apply(this, ["hoge"]); })();
a instanceof Hoge; // => false
最初のパターンがうまくいっていた(Array
のインスタンスが返ってきていた)ように見えたのは、組み込みの Array
関数が「配列オブジェクト」を返すためです。実はnew
演算子の結果は、コンストラクタ関数がオブジェクトを返す場合はそのオブジェクトになり、それ以外の場合にコンテキストオブジェクト(i.e. this
)になります。
つまり、最初の例は実は以下のコードとほぼ等価だったということになります:
a = Array.apply(null, [1,2,3]);
では逆に、上で挙げた Hoge
の例の場合はどうでしょうか。この関数 Hoge
は return
文をもたないので、返り値は undefined
でオブジェクトではありません(もちろん Hoge
のインスタンスでもない)。そのため、new
演算子を使うと、新しく生成された空のコンテキストオブジェクト this
に Hoge
を適用してプロパティが追加されたものができあがります。これは Hoge
のインスタンスではなく、外側の無名関数のインスタンスになります。結局、次のコードと等価なことをしていることになります:
a = new (function () { return; })();
Hoge.apply(a, ["hoge"]);
こういうキモい挙動が色々あってメンドクサイので、「邪悪な new
演算子は使うな!」という人もいます(つまり Crockford 先生のことですが)。
では任意のコンストラクタ関数に引数を配列で与えつつ new
するにはどうすればいいか?その答えの一つが、JavaScriptでリフレクション - Qiita の方でコメントしたように、Function.prototype.bind
を活用することです。
var a = new (Hoge.bind.apply(Hoge, [null].concat(["hoge"])))();
a instanceof Hoge; // => true
まぁ bind
を使わないといけないので、古いブラウザだと動かないかもしれません。MDN に載ってるように等価なメソッドを定義してやれば行けると思いますが。
# 私はこれ以外には eval
で無理を通す方法しか知りません。