kidoOooOoooOOom

IT系で開発やってます

Effective JavaSceipt 読書メモ 項目22~26

第3章 関数の扱い

項目22 可変長引数関数を作るには、argumentsを使う

JSでは、すべての関数に暗黙のうちに提供するローカル変数 argumentsが存在する。このargumentsを使うと、下記のように可変長引数関数を自作できる。

function average() {
  for (var i = 0, sum = 0, n = arguments.length; i < n; i++) {
    sum += arguments[i];
  }
  return sum / n;
}

console.log(average(1,2,3,100)); // 26.5

ただし上記の場合、引数に配列を使いたい場合はapplyメソッドを使わなければならず、クライアント側から使うのに面倒なケースがある。
そこでお勧めされているのが、明示的に配列を受け取る「固定アリティ」関数と、「可変アリティ」の両方を提供することでクライアントの便宜をはかることができる。

// 固定アリティ
function averageOfArray(a) {
  for (var i = 0, sum = 0, n = a.length; i < n; i++) {
    sum += a[i];
  }
  return sum / n;
}

// 可変アリティ
function average() {
  return averageOfArray(arguments);
}

console.log(average(1,2,3,100)); // 26.5
console.log(averageOfArray([1,2,3,100])); // 26.5

項目23 argumentsオブジェクトを書き換えない

argumentsオブジェクトは配列のように見えるが、常に配列のように振る舞うとは限らない。arugmentsオブジェクトは標準Array型ではないため、例えばarguments.shift()を直接呼び出す事ができない。
それでもcallメソッドを用いて配列のsliceメソッドを適用すると、標準Array型のインスタンスにコピーすることができる。

function argsTest() {
  var args = [].slice.call(arguments, 2);
  console.log(args); // 30,40,50
}
argsTest(10,20,30,40,50);

項目24 argumentsへのリファレンスは変数に保存する

iteratorの挙動を期待した関数をargumentsを用いて下記のように実装する。

function values() {
  var i = 0, n = arguments.length;
  return {
    hasNext: function() {
      return i < n;
    },
    next: function() {
      if (i >= n) {
        throw new Error("end of iteration");
      }
      return arguments[i++];
    }
  }
}

しかし、上記のコードは期待した結果を得られない。

var it = values(1,2,3,4,5,6);
console.log(it.next()); // undefined
console.log(it.next()); // undefined
console.log(it.next()); // undefined

これは、argumentsがit.next()の引数をアクセスしてしまっているからである。ネストした関数からargumentsを参照する場合は、values()メソッド内のローカル変数に結合しておく必要がある。

項目25 固定レシーバを持つメソッドを抽出するにはbindを使う

関数について、そのレシーバを特定のオブジェクトに結合(bind)するバージョンを作成する場合、ES5ではbindメソッドを用いればよい。
例えば、普通に呼んでしまうとグローバルオブジェクトをレシーバーにしてしまうforEachメソッドに対し、bufferオブジェクトをレシーバにしたい場合は下記のように書く。

hoge.forEach(buffer.add.bind(buffer));

ただし、bindは新しいadd関数を作成するのであり、buffer.add関数を書き換えるわけではないとのこと。

項目26 関数をカリー化するには、bindを使う

下記のコードは、simpleURLメソッドに対し、paths.mapに無名関数を渡して反復処理させるコードである。

function simpleURL(protocol, domain, path) {
  return protocol + "://" + domain + "/" + path;
}

var paths = ["aaa", "bbb"];
var urls = paths.map(function(path) {
  return simpleURL("http", "hoge", path);
})

console.log(urls); // [ 'http://hoge/aaa', 'http://hoge/bbb' ]

しかし、このコードでは "http"と"hoge"の文字列がどの繰り返しでも同じであり、本当に必要なのは3つ目のpath引数だけである。このようなケースの時、bindを用いてカリー化を行うと効率的に書ける。

var urls2 = paths.map(simpleURL.bind(null, "http", "hoge"));
console.log(urls2); // [ 'http://hoge/aaa', 'http://hoge/bbb' ]

通常のようにbindの第1引数はレシーバの値を提供する。今回の例ではsimpleURL関数がthisを参照しないため、nullかundefinedを使うのが通例となるとのこと。
simpleURLに渡される引数は、simpleURL.bindの引数に新しい関数に対して提供された引数を連結したものになる。つまり、simpleURL("http", "hoge", path)となる。
このように関数に対して、その引数の部分集合を結合させるテクニックはカリー化と呼ばれる。カリー化を使うと、関数の委任を明示的なラッパー関数を使うよりも決まり文句が少ない簡潔な方法で実装できる。