[JavaScript] arguments変数を本物の配列に変換する方法

2017年1月16日月曜日

JavaScript

JavaScriptで任意の関数の戻り値の真偽を逆転するような関数を生成するには以下のようになる。
function not(pred) {
  return function(args) {
    return !pred.apply(null, arguments);
  }
}

var p = not(function () { return true; });
console.log(p());
// false

p = not(function (x) { return x; });
console.log(p(false));
// true

arguments変数はすべての関数で特別に用意されるローカル変数で、lengthプロパティを持っているし、一見配列のように振る舞う。
例えば、渡されたすべての引数をconsoleに出力したければ、こうなる。
function log(args) {
  var i;
  for (i = 0; i < arguments.length; i++) {
    console.log(arguments[i]);
  }
}

log('banana', 'orange', 'grape');
// banana
// orange
// grape

でも、argumentsは本物の配列ではないので、forEachメソッドを使おうとするとうまくいかない。
function log1(args) {
  arguments.forEach(function (x) { console.log(x); });
}

log1('banana', 'orange', 'grape');
// TypeError: arguments.forEach is not a function

なので、Arrayコンストラクタを使って、配列に変換してみよう。
function log2(args) {
  args = Array.apply(null, arguments);
  args.forEach(function (x) { console.log(x); });
}

log2('banana', 'orange', 'grape');
// banana
// orange
// grape

これだと、うまくいくように見えるけど、引数として数値型1つのみをを渡した場合にうまくいかない。
log2(5);
// なにも出力されない!

Arrayコンストラクタは数値型の引数が1つだけ渡された場合は配列の長さとして扱ってしまうためだ。
new Array(5) は [undefined, undefined, undefined, undefined, undefined]になってしまう。
おまけにforEachメソッドは要素がundefinedの場合、コールバック関数を呼び出してくれないので、なにもconsoleに出力されなくなってしまう。

これを防ぐためにはargements.lengthが1の時は特別扱いしないといけない。
function log3(args) {
  args = arguments.length === 1 ? [arguments[0]] : Array.apply(null, arguments);
  args.forEach(function (x) { console.log(x); });
}
log3(5);
// 5