松山事務所の石丸です。
以前JavaScriptで可変長引数を受け取って別の関数へ渡したいという記事で、普通の関数をapplyしましたが、コンストラクタをapplyするにはどうしたらよいか調べていたら、stack overflowでとてもいい記事に出会えました。
今回もES5.1です。
試行錯誤
まずは自力でいろいろ試してみました。
1
2
3
4
5
6
7
8
9
10
11
12
|
function Dog(name) {
console.log(name);
}
var pochi = new Dog.apply(null, ["ポチ"]);
// Dog.apply is not a constructor
var john = new Dog.prototype.constructor.apply(null, ["ジョン"]);
// Uncaught TypeError: Dog.prototype.constructor.apply is not a constructor
var taro = Dog.prototype.constructor.apply(null, ["太郎"]);
console.log(taro);
|
ポチもジョンもコンストラクタじゃないと怒られてしまいます。
太郎はただ戻り値のない関数を呼び出しただけなので taro
が undefined
になってしまいました。
コンストラクタといってもそれ自体はただの関数なので、new演算子とapplyをどう組み合わせるかというところがキモですね。
「Function.prototype.bindを使うと良い感じに書けるぜ」
stack overflow – Use of .apply() with ‘new’ operator. Is this possible?
ここで一番人気の回答が次のコードです。
1
2
3
4
5
6
7
8
|
function newCall(Cls) {
return new (Function.prototype.bind.apply(Cls, arguments));
// or even
// return new (Cls.bind.apply(Cls, arguments));
// if you know that Cls.bind has not been overwritten
}
var s = newCall(Something, a, b, c);
|
何が起こるのかわからない魔法のようなコードですが、
この回答にはとても丁寧な補足解説があるのでグーグル翻訳を駆使してこのコードを理解していきます。
補足解説翻訳
限られた数の引数を取る関数に対してnewを実行する必要があります。
bindメソッドを使うと、次のようにすることができます。
1
2
|
var f = Cls.bind(anything, arg1, arg2, ...);
result = new f();
|
newキーワードがfのコンテキストをリセットするので、anythingパラメータはあまり重要ではありませんが、構文上の理由から必要です。
可変引数を渡す必要があるので、ちょっとしたトリックを使います。
1
2
|
var f = Cls.bind.apply(Cls, [anything, arg1, arg2, ...]);
result = new f();
|
それを関数で囲みましょう。 Clsはarugment 0として渡されるので、それは anything に相当します。
1
2
3
4
|
function newCall(Cls /*, arg1, arg2, ... */) {
var f = Cls.bind.apply(Cls, arguments);
return new f();
}
|
一時的な変数 f は必要ありません。
1
2
3
|
function newCall(Cls /*, arg1, arg2, ... */) {
return new (Cls.bind.apply(Cls, arguments))();
}
|
Cls.bindが上書きされている可能性があるので、Function.prototype.bindで置き換えると、最終的に回答のコードになります。
1
2
3
|
function newCall(Cls) {
return new (Function.prototype.bind.apply(Cls, arguments));
}
|
機械翻訳感が残っていますが伝わりますよね?
補足
トリックの部分が結構大事です。
newCallという関数にすることでnewCallの第一引数のClsがうまいことbindの第一引数になっているので、関数にしない場合はapplyの第二引数の配列(のようなオブジェクト)の先頭には anything に相当するなにかを渡してあげる必要があります。
1
2
|
var hachi = new (Function.prototype.bind.apply(Dog, [null, "ハチ"]));
console.log(hachi);
|