Edited at

new 演算子を使いつつ可変長引数っぽいことをするには?

More than 5 years have passed since last update.

applyメソッドとnewを使ってインスタンスを生成するコードの例 - Qiita へのコメントです。長くなったので記事にしました。


new-and-apply-pattern.js

var a = new (function () { return Array.apply(this, [1,2,3]); })();


これ、一見うまくいってるように見えますが、間違ってますね。


bad-example.js

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 の例の場合はどうでしょうか。この関数 Hogereturn 文をもたないので、返り値は undefined でオブジェクトではありません(もちろん Hoge のインスタンスでもない)。そのため、new 演算子を使うと、新しく生成された空のコンテキストオブジェクト thisHoge を適用してプロパティが追加されたものができあがります。これは Hoge のインスタンスではなく、外側の無名関数のインスタンスになります。結局、次のコードと等価なことをしていることになります:

a = new (function () { return; })();

Hoge.apply(a, ["hoge"]);

こういうキモい挙動が色々あってメンドクサイので、「邪悪な new 演算子は使うな!」という人もいます(つまり Crockford 先生のことですが)。


では任意のコンストラクタ関数に引数を配列で与えつつ new するにはどうすればいいか?その答えの一つが、JavaScriptでリフレクション - Qiita の方でコメントしたように、Function.prototype.bind を活用することです。


new-and-bind-pattern.js

var a = new (Hoge.bind.apply(Hoge, [null].concat(["hoge"])))();

a instanceof Hoge; // => true

まぁ bind を使わないといけないので、古いブラウザだと動かないかもしれません。MDN に載ってるように等価なメソッドを定義してやれば行けると思いますが。

# 私はこれ以外には eval で無理を通す方法しか知りません。



参考文献