読者です 読者をやめる 読者になる 読者になる

あと味

たくさん情報を食べて、たくさん発信すると、あとになって味わい深い。

Underscore.jsの全メソッドを表にまとめてみた

JavaScript Advent Calendar 2011 (フレームワークコース) の9日目です。

せっかくの機会だったので、ものすごく気になってたけど、触る機会がなかった、Underscore.jsをいろいろと弄ってみました。

配列関係の便利メソッドの集合ライブラリなイメージでしたが、タイマーやユーティリティ関数、条件判断関数など、いろいろ機能があって面白いですね。

とりあえず、ひと通り実行しながら、すべての関数を触ってみました。

非常に見難くて恐縮ですが、以下のGoogleスプレッドシートにいろいろとメモを取っていったので、参考にしてください。

間違いなどあるかもしれませんし、自分自身理解しきっていないため説明がおかしい箇所があるかもしれません。

その際はご指摘いただけると助かります。超めんどくさかったので、自主的なアップデートは、たぶん、しません...

リンク先: Underscore.jsの機能表

機能表の見方

左の列から順に、

  1. カテゴリ
  2. メソッド名
  3. 書式
  4. 説明
  5. aliases
  6. native method変換
  7. 備考
  8. 擬似的な型(Ocamlっぽく)

となっています。

各列の説明をすると以下のとおりです。

カテゴリ

Underscore.jsのサイトどおりのカテゴリ。

メソッド名

メソッド名。

書式

実行例を引数も含めて書いたもの。

説明

自分で考えた説明(ここは間違っている可能性も高い箇所です)

aliases

別名が定義されているものはそれを列挙。

native method変換

内部でnative methodに変換しているものは、変換したメソッド名。

備考

気づいた点など。

擬似的な型

Ocamlを勉強しているので、それ風に書いてますが、オリジナルの型を勝手に付けてますし、合ってるか間違ってるかの判断もできないレベルです。書き始めちゃったら後戻りできなくなったし、かと言って消すのももったいないし、私のためのメモというか...

えっと、無視してください。(もし、Ocamlに造詣の深い方がいらっしゃったら、助言いただけるとうれしいです。)

オプション引数[context]について

書式の列にオプション引数を使っているメソッドがありますが、個人的に[context]はわかりにくかったので、解説します。

Collectionのカテゴリには、each, map, reduce, reduceRight, find, filter, all, any, max, min, sortByに。Utilityのカテゴリには、templateにあります。

templateはちょっと違う意味で使われていますが、[context]に指定するものはオブジェクトになります。

eachで例を出すと以下のような感じです。

var messs = ['My', 'name', 'is', 'taiju'];

var Blog = function(author) {
  this.author = author;
};

var blog = new Blog('taiju');

_.each(messs, function(mess) {
  console.log(this.author + ' said: ' + mess);
}, blog);

eachに[context]を渡した例です。見てわかるように、thisの値に[context]で指定したオブジェクトが束縛されます。

内部でthisの値を使うような関数を渡す時に、[context]を使います。

ネイティブメソッド変換について

JavaScriptが持つメソッド(以後、ネイティブメソッドと表記)で処理を実現できるものに関しては、内部でネイティブメソッドに変換して実行しているものがあります。詳しくは、表中のnative method変換の列をご参照ください。

オリジナルの処理よりも、最適化されたネイティブメソッドが使われた方がやはり効率的です。

eachメソッドでベンチを取ってみました。eachメソッドはArray.prototype.forEachが使える環境では、それを使うように最適化されています。

eachメソッドのベンチ - jsdo.it - share JavaScript, HTML5 and CSS

Array Likeオブジェクトでは、forEachメソッドが使えませんので、パフォーマンスが低くなっています。eachメソッドは配列だけではなく、オブジェクトにも使うことができますが、オブジェクト(Arrayオブジェクトを除く)も、forEachメソッドが使えませんので、パフォーマンスが低くなります。(上記のベンチでは微妙な差ですが、要素数が多いと、よりハッキリするかもしれません。Array Likeオブジェクトは最も効率が悪いですね。)

パフォーマンスが気になるのであれば、_.toArrayメソッドなどを適宜利用して、ネイティブメソッドが使われるようにプログラミングした方が良いかなと思います。とは言え、どれとは言いませんが、そもそもArray.prototype.forEachを持っていないブラウザ使ってる時は無意味ですね。

ソート済リストの最適化

一部、ソート済リストを渡すことでアルゴリズムがより効率の良いものに最適化されるメソッドがあります。

具体的には、uniqとindexOfです。

これらのメソッドに配列を渡す際、ソート済の配列を渡して、かつ、[isSorted]オプションにtrueを渡すとより効率の良いアルゴリズムが選択されます。

具体的にはuniqの場合、最適化すると、配列の走査が一度で済みます(最適化されない場合は、要素の数だけ配列全体を走査する)。

indexOfの場合は、最適化すると、線形探索ではなく、二分探索になります。

ベンチも取ってみました。

[Underscore.js]indexOfのベンチ - jsdo.it - share JavaScript, HTML5 and CSS

indentityメソッドについて

引数をそのまま返す関数を返す関数です。

関数型言語では、一般的に恒等関数と呼ばれるもので、ユーザーが使うことはないと思いますが、高階関数を利用するシーンにおいて、何も作用を及ぼさないが、関数全体を壊しもしない関数ということで、利用価値があるのだと思います。

実際に、内部的に、iteratorのデフォルト値として使われるシーンがあります。

OOPスタイルのプログラミングについて

Underscore.jsでは、OOPスタイルのプログラミングができるようになっています。

サイトに載っているサンプルコードを以下にコピペします。

var lyrics = [
  {line : 1, words : "I'm a lumberjack and I'm okay"},
  {line : 2, words : "I sleep all night and I work all day"},
  {line : 3, words : "He's a lumberjack and he's okay"},
  {line : 4, words : "He sleeps all night and he works all day"}
];

_(lyrics).chain()
  .map(function(line) { return line.words.split(' '); })
  .flatten()
  .reduce(function(counts, word) {
    counts[word] = (counts[word] || 0) + 1;
    return counts;
}, {}).value();

// => {lumberjack : 2, all : 4, night : 2 ... }

_()の中にコレクションを挿入して、そのままメソッドチェインで処理をつなげていますね。

_()の返り値は、this._wrappedに引数のオブジェクトが代入されたラッパーオブジェクトですが、ユーザーがUnderscoreオブジェクトを拡張する用途にも使う、mixinメソッドを使って、wrapperオブジェクトのprototytpeオブジェクトに、Underscoreオブジェクトに追加したメソッドが全て結合されています。

_.prototypeとwrapper.prototypeは共通の参照を持っているので、どちらの方法でも同じメソッドが使えるようになっています。

引数の順番について

Underscore.jsでは、コレクション、配列を指定する扱うメソッドに関して、第一引数がコレクション、または配列になっているメソッドがほとんどです。

これには引数の順番に一貫性があってわかりやすいという利点がある他、メソッドチェーンを実行できるようにするための、chainメソッドにおいても重要なポイントになっています。

前述した、_()の返り値であるラッパーオブジェクトは、chainメソッドを持ちます。chainメソッドを実行すると、内部のchainフラグが立ち、それ以後、chainフラグが立っている限り、メソッドをチェインでつなげることができます。

チェイン化されたラッパーオブジェクトは、各メソッドの実行時、そのメソッドの第一引数に、ラップされたコレクションを渡す処理になっています。そのため、チェインでつなげるメソッドの第一引数は、関数オブジェクトから渡せばよく、関数適用後のコレクションは、ラップされたコレクションに上書きされます。これによって、次々とメソッドを実行していく過程で、ラップされたコレクションを書き変えていくことができます。

動的型付けで、prototypeベースのいつでもどこでも拡張していいよ!というゆるゆる言語なお陰で、こういう柔軟な機能が提供できるってことで、改めて、JSすげ、と思う次第です。

まとめ

Underscore.jsは、個人的に超好きそうなライブラリだと思っていましたが、弄ってみて、やはり超好きなライブラリでした。

関数プログラミングに興味がある人は、好きになる可能性が高いと思います。

今回はAdvent Calendarの縛りで、公開日が決まっていたため、一部記事の内容を妥協せざるを得ませんでしたが、後日、Underscore.jsのソースコードを参照しながら、もうちょっと踏み込んだ内容の記事が書けたらいいなと思います。

特にタイマー系のメソッドが結構トリッキーだったり、公式のサンプルコードではイメージがわかなかったり、オプション引数の使い方がわからずじまいになったりするので、オリジナルのサンプルコードを用いて解説する記事があるといいなと、個人的にも思います。

無駄にUnderscore.jsについての知識がついてしまいましたが、ありがたい話です。そんなキッカケ作りに、Advent Calendarはいかがでしょうか。

今年度分もまだ、空いてます!!