Arrayっぽいこんなクラスがあったとする
function Arraylike() {
for (var i = 0, len = arguments.length; i < len; i++) {
this[i] = arguments[i];
}
this.length = len;
}
Arraylike.prototype.join = function(separator) {
return [].join.call(this, separator);
}
// new Arraylike(1, 2, 3).join(', ') => "1, 2, 3"
var array = [1, 2, 3]
みたいな配列があって、これをArraylike
のコンストラクタに渡したいとき、どうしたらよいのか。
簡単にできそうでできないのでわりと困るけど、何個かそれっぽいやりかたはある。
Function.prototype.bind を使う方法
ES5で定義されたFunction.prototype.bind
を使えば、実はうまいことできる。
var Binded = Arraylike.bind.apply(Arraylike, [null].concat(array));
var instance = new Binded();
console.log(instance.join(', ')); //=> "1, 2, 3"
console.log(instance instanceof Arraylike); //=> true
ただし、この方法はちょっと古い iOS や Android、IE8では使えない模様。
http://kangax.github.io/es5-compat-table/
http://d.hatena.ne.jp/zentoo/20120819/1345378440
ダミーのコンストラクタを作る方法
プロトタイプを空のコンストラクタに付け替える方法。
ただし、この方法では組み込みクラスで使えなかったりする。
function Dummy() {}
Dummy.prototype = Arraylike.prototype;
var instance = new Dummy();
Arraylike.apply(instance, array);
console.log(instance.join(', ')); //=> "1, 2, 3"
console.log(instance instanceof Arraylike); //=> true
evalする方法
弾先生のブログにヒントが
http://blog.livedoor.jp/dankogai/archives/51758978.html
var argsValue = array.map(function(v, i){
return 'args[' + i + ']';
}).join(', ');
var instance = Function('ctor', 'args',
'return new ctor(' + argsValue + ');')(Arraylike, array);
console.log(instance.join(', ')); //=> "1, 2, 3"
console.log(instance instanceof Arraylike); //=> true
結論
eval
する方法が一番対応ブラウザ多くて素直だと思う。
ネックは速度だろうけど、生成した関数をキャッシュしておけば解決できそう。
Functionにメソッド追加して、こんな感じでどうでしょうか。
なにか問題があったり、もっとスマートなやり方があれば教えてください。
Object.defineProperty(Function.prototype, 'applyConstructor', (function(){
var cache = {};
function generate(len) {
var argsValue = [];
for (var i = 0; i < len; i++) {
argsValue.push('args[' + i + ']');
}
return Function('ctor', 'args', 'return new ctor(' + argsValue.join(',') + ')');
}
return {
value: function(args) {
var len = (args != null && args.length) || 0;
return (cache[len] || (cache[len] = generate(len)))(this, args);
}
};
}()));
console.log(Arraylike.applyConstructor(array).join(', ')); // => 1, 2, 3