はじめに
Array.prototype.mapやfilter、reduceが当たり前に使えるようになった今、
わざわざユーティリティライブラリを読み込む必要はあるのか——そう思う方も
多いはずです。ですが、オブジェクトのキーだけを取り出したい、配列を条件で
グルーピングしたい、関数の呼び出し頻度を間引きたいといった場面では、
標準APIだけでは意外と手数がかかります。
Underscore.jsは、そうした「痒いところ」を埋めるために2009年に生まれた
JavaScriptライブラリです。Lodashの前身であり、Backbone.jsを長年支えてきた
このライブラリは、今もnpmで活発にダウンロードされ続けています。組み込み
オブジェクトを拡張しない設計、依存ゼロで1ファイルに収まる軽さは、令和の
今でも十分に通用する価値です。
とはいえ、読むより触った方が早いと思います。_.chainや_.groupByで配列を
どう変形できるか、実際に動くサンプルで確かめられます。先に挙動を見たい方は
こちらからどうぞ。
Underscore.jsとは
Underscore.jsは、CoffeeScriptやBackbone.jsの作者としても知られるJeremy
Ashkenas氏が2009年に公開したJavaScriptユーティリティライブラリです。
配列・オブジェクト・関数を扱うための100個以上の関数を、グローバルの
_という1つのオブジェクトにまとめて提供します。
後発のLodashはUnderscore.jsのAPIを踏襲して生まれたライブラリであり、 Backbone.jsは今も唯一の依存先としてUnderscore.jsを使っています。つまり Underscore.jsは、多くのJavaScriptユーティリティライブラリの「原点」に あたる存在です。
主な特徴
- 依存ゼロで軽量 - 本体は1ファイルに収まり、他のライブラリへの依存を持ちません
- 組み込みオブジェクトを拡張しない -
Array.prototypeなどを汚染せず、_(obj).method()の形で安全に使えます - 関数型スタイルのユーティリティ -
map・filter・reduceに加え、groupBy・pluck・sortByなど実務でよく使う操作が揃っています - 軽量なテンプレートエンジン内蔵 -
_.templateでJavaScriptテンプレートを関数にコンパイルできます - チェイン記法 -
_.chain()で複数の操作を読みやすくつなげられます
インストール
npmでインストールする場合は次のコマンドを実行します。
npm install underscore
ブラウザで直接読み込む場合はCDN経由でも利用できます。
<script src="https://cdn.jsdelivr.net/npm/underscore@1.13.8/underscore-min.js"></script>
Underscore.jsのサンプルを動かす
以下は、社員データの配列を_.chainでつないで部署ごとに_.groupByし、
_.pluckで名前だけを取り出すサンプルです。入力欄の絞り込みキーワードを
書き換えると、_.filterの条件が変わり結果がその場で更新されます。
const people = [
{ name: 'Aoki', dept: 'Sales' },
{ name: 'Ito', dept: 'Dev' },
{ name: 'Ueda', dept: 'Sales' },
]
// filter → groupBy → pluck をチェインでつなぐ
const result = _.chain(people)
.filter((p) => p.name.includes('A'))
.groupBy('dept')
.value()
実際に動かせるものが下です。テキスト欄に名前の一部を入力すると、
_.filterが絞り込み、_.groupByが部署ごとに再集計します。
キーワードを空にすると全員が表示され、'e'のように条件を変えると
_.filterが該当者だけを残します。_.chainは途中の各メソッド呼び出しを
つなげて書けるので、filter→groupBy→mapObjectのような多段の変換も
1文で見通しよく書けるのが特徴です。
基本的な使い方
Underscore.jsの基本は、配列やオブジェクトを_の関数に渡すだけです。
import _ from 'underscore'
const numbers = [1, 2, 3, 4, 5]
_.map(numbers, (n) => n * 2) // [2, 4, 6, 8, 10]
_.filter(numbers, (n) => n % 2 === 0) // [2, 4]
_.reduce(numbers, (sum, n) => sum + n, 0) // 15
const user = { id: 1, name: 'Taro', age: 28 }
_.keys(user) // ['id', 'name', 'age']
_.pick(user, 'id', 'name') // { id: 1, name: 'Taro' }
_.omit(user, 'age') // { id: 1, name: 'Taro' }
map・filter・reduceはArrayの標準メソッドと同名ですが、Underscore.js版は
配列だけでなくオブジェクトにもそのまま使える点が異なります。
実践的なユースケース
検索入力のデバウンス
検索ボックスに文字を打つたびにAPIを叩くと、サーバーへの負荷も表示のちらつきも
無視できません。_.debounceを使うと、入力が一定時間止まってから初めて処理を
実行できます。
import _ from 'underscore'
const search = _.debounce((keyword) => {
console.log('検索実行:', keyword)
}, 300)
input.addEventListener('input', (e) => search(e.target.value))
このパターンをブラウザで確認できるようにしたのが次のサンプルです。入力欄に
連続して文字を打つと、_.debounceによって最後の入力から300ミリ秒経つまで
「検索実行」ログが遅延されるのが分かります。
打鍵のたびにログが増えるのではなく、タイピングが止まってから1回だけ
「検索実行」が記録される点に注目してください。300という数値を1000に
変えると、待ち時間が伸びて実行される頻度がさらに下がります。
_.templateによるHTMLテンプレート生成
APIから取得したデータをHTMLに埋め込みたいとき、文字列連結を重ねると
コードが読みにくくなりがちです。_.templateを使うと、ERBライクな構文で
テンプレートを1つの関数にコンパイルできます。
import _ from 'underscore'
const compiled = _.template(
'<li><%- name %>さん(<%= age %>歳)</li>'
)
compiled({ name: 'Suzuki', age: 30 })
// => '<li>Suzukiさん(30歳)</li>'
<%- %>はHTMLエスケープしながら値を埋め込み、<%= %>はそのまま出力します。
以下のサンプルでは、名前を入力するたびに_.templateでコンパイルした関数を
呼び出し、一覧に追加します。名前欄に<script>のようなタグを含む文字列を
入れても、<%- %>部分はエスケープされるため安全に表示されます。
<%- name %>の部分を<%= name %>に書き換えると、エスケープが外れて
入力した文字列がそのままHTMLとして解釈されるようになります。テンプレート
エンジンを使うときは、ユーザー入力を表示する箇所では常にエスケープ側の
構文を使うのが安全です。
まとめ
Underscore.jsは、標準の配列メソッドだけではカバーしきれない グルーピング・デバウンス・テンプレート生成といった処理を、依存ゼロの 軽量なパッケージで提供してくれるライブラリです。LodashやBackbone.jsの 土台になった歴史を持ちながら、今もそのままの形で現役で使われています。
すでにBackbone.jsのプロジェクトを持っている方はもちろん、lodashを
入れるほどではない小規模なプロジェクトでも、_.chainや_.debounceのような
使い勝手のよい関数だけをつまみ食いする形で導入してみてはいかがでしょうか。
