Underscore.jsの入り口

第3回underscore.jsの関数とユーティリティとChaining

underscore.jsの関数とユーティリティ(テンプレート等)とChainingの機能をご紹介します。

関数の機能

JavaScriptで関数型プログラミングのテクニックが使いやすくなる便利な関数が揃っています。引数に1つ以上の関数を受け取り、その関数に対して処理を行い、新しい関数を返します。

今回は、関数をオブジェクトで束縛する_.bind()、関数の部分適用を行う_.partial()、メモ化を行う_.memoize()などを紹介していきます。その他の機能につきましては記事の最後にまとめましたので、参考にしていただければと思います。

_.bind()

書式
_.bind(function, object, [*arguments])

指定したobjectにfunctionを束縛(bind)して新しい関数を返します。よって新しい関数の中で使われるthisは指定したobjectになります(functionが呼び出される際のコンテクストを指定できます⁠⁠。

argumentsを渡した場合は引数も部分適用(partial application)する事ができます。ネイティブの機能でbind()が使える環境ではそちらを使います。

var message = function(greeting){
    return greeting + this.name + '!!!'
};

var hello_message = _.bind(message, {name: 'makoto'}, 'hello!!'); 
//message関数を指定したオブジェクトで束縛(bind)する

hello_message();
//=> 'hello!!makoto!!!'
関数の部分適用(partial application)

関数型プログラミングで紹介されるテクニックの1つです。既存の関数に引数をあらかじめ指定して新しい関数(ラッパー関数)を作ります。同様の関数をほとんど同じ引数で使っていたらその関数は部分適用する候補になります。

_.partial()

書式
_.partial(function, [*arguments])

指定した関数の引数を部分適用した新しい関数を返します。

var add = function(a, b) { return a * b; };

add(16,11);
//=> 176

//add関数の引数aを部分適用します。
var add11 = _.partial(add, 11); 

add11(16);
//=> 176


var message = function(greeting){
    return greeting + ' ようこそお越し下さいました。'
};

//message関数の引数をそれぞれ部分適用します。
var daytime_message = _.partial(message,'こんにちは');
var night_message   = _.partial(message,'こんばんは');

daytime_message();
//=> "こんにちは ようこそお越し下さいました。"

night_message();
//=> "こんばんは ようこそお越し下さいました。"

_.memoize()

書式
_.memoize(function, [hashFunction])

引数に渡したfunctionの結果をメモ化する関数として新しく返します。2回目からは問い合わせ内容がキャッシュされるので結果を返すのが高速化されます。

第2引数のhashFunctionはメモ化する際にメモ化されるデータのキーを設定するために使われます。デフォルトでは_.identityにfunctionの引数を渡したものがメモ化されるデータのキーとして使われます。

・メモ化

一度呼び出された結果をそのときの引数と共に記憶しておき、後で同じ引数で呼び出されたときいんは処理を行わずにその記憶していた値を返します。実行速度の遅い計算を何度か行う場合、2回目からは問い合わせ内容がキャッシュされるので結果を返すのが高速化されます。

下記、使用例になります。

//データになります。
var members = [
  { 'name': 'chiba', 'age': 25 },
  { 'name': 'kuhara', 'age': 24 },
  { 'name': 'abe', 'age': 26 },
  { 'name': 'ueno', 'age': 27 }
];

//データを取得する関数です
var get = function(name){
    return _.find(members,
        function(member){ 
            return member.name == name;
        })
};

//データが取得できます。
get('chiba');
// =>  {name: "chiba", age: 25}


//メモ化関数をかえします。
var getmemo = _.memoize(get);

//データが取得できます。データがキャッシュされます。
getmemo('chiba');
// =>  {name: "chiba", age: 25}

//二回目以降はキャッシュから取得します。
getmemo('chiba');
// =>  {name: "chiba", age: 25}


//フィボナッチ数列の計算のメモ化の例です。
var fibonacci = _.memoize(function(n) {
  return n < 2 ? n: fibonacci(n - 1) + fibonacci(n - 2);
});

ユーティリティの機能

その他の便利なユーティリティの機能をご紹介します。エスケープ処理やテンプレート機能などが用意されています。機能につきましては、記事の最後にまとめましたので、参考にしていただければと思います。

_.uniqueId()

書式
_.uniqueId([prefix])

ユニークなIDを生成します。

prefixが渡された場合、IDに追加されます。

クライアント側のモデルや唯一のDOM要素のために便利です。

_.uniqueId('cont_');
//=> cont_10

_.uniqueId('cont_');
//=> cont_11

_.uniqueId('cont_');
//=> cont_12

_.escape()

書式
_.escape(string)

HTMLに挿入するために、文字列 &, <, >, ", ' を &, <, >, ", 'にエスケープします。

var str = '"Asai", "Nakamura", & "Terui"';

_.escape(str);
//=> "Asai", "Nakamura", & "Terui"

_.unescape()

書式
_.unescape(string)

HTMLに挿入するためにエスケープされた文字列を置換してもとに戻します。

var str = '"Asai", "Nakamura", & "Terui"';

_.unescape(str);
//=> "Asai", "Nakamura", & "Terui"

_.template()

書式
_.template(templateString, [data], [settings])

テンプレート(第一引数’templateString⁠⁠)を引数で置き換えるテンプレート関数を返します。JSONデータソース等からの複雑になりがちなHTMLへの変換を便利にします。

テンプレート関数の置き換えは

<%=  %>

で行います。

下記は例になります。

var template = _.template("hello! <%= name %>!!");

template({name: 'ai'});
//=> hello! ai!!

ただし実際にHTMLのテンプレート関数を作成する際は上記ではエスケープは行われないため、

<%-  %>

で行ってください。 上記の指定で_.escape()を適用します。

下記は例になります。

var span_template = _.template("<span><%- span_val %></span>");

span_template({span_val: '<script type="text/javascript" src="underscore.js"></script>'});
//=> <span><script type="text/javascript" src="underscore.js"></script></span>

JavaScriptの制御構文も入れる事ができます。

<%  %>

で行ってください。コレクションの置き換えなどができます。

下記は例になります。

var members = [
  { 'name': 'chiba', 'age': 25 },
  { 'name': 'kuhara', 'age': 24 },
  { 'name': 'abe', 'age': 26 },
  { 'name': 'ueno', 'age': 27 }
];

var list = "<ui>" + 
                "<% _.each(members, function(member) { %>" + 
                "<li><%- member.name %>:<%- member.age %></li>" + 
                "<% }); %>" + 
            "</ui>";

_.template(list, members);
//=> <ui><li>chiba:25</li><li>kuhara:24</li><li>abe:26</li><li>ueno:27</li></ui>

Chainingの機能

Chainingの機能をご紹介します。Chainingは、Underscore.jsの関数を連続して呼び出す方法です。コードを単純化することができます。

_.chain()

書式
_.chain(object)

objectをラップしたオブジェクトを返し、オブジェクトのメソッドを返します。ラップされたオブジェクトはUnderscore.jsの関数とArray prototypeのメソッド(reverse , push等)持っています。

var members = [
  { 'name': 'chiba', 'age': 25 },
  { 'name': 'kuhara', 'age': 24 },
  { 'name': 'abe', 'age': 26 },
  { 'name': 'ueno', 'age': 27 }
];

//一番若いメンバーを抽出します。
var youngmember = _.chain(members)
    .sortBy('age')
    .map(function(member) { return member.name + ':' + member.age; })
    .first()
    .value(); //ラップしたオブジェクトの値を返します。

youngmember;   
//=>  kuhara:24


//_.tap を使う事でChainingの途中の値の確認が行えます。
_.chain(members)
    .sortBy('age')
    .map(function(member) { return member.name + ':' + member.age; })
    
    // "kuhara:24,chiba:25,abe:26,ueno:27"がalertされます。
    .tap(alert) 
    .first()
    .value();

各種機能のご紹介

今回取り上げきれなかった各種機能を表にまとめて紹介します。

 コレクションの機能
メソッド名説明Alias
_.each(list, iterator, [context])listの各要素に対してiteratorを実行します。iteratorの引数は(element, index, list)になります。ネイティブの機能でforEach()が使える環境ではそちらを使います。forEach
_.map(list, iterator, [context])listの各要素に対してiteratorを実行した新しい配列を返します。iteratorの引数は(element, index, list)になります。ネイティブの機能でmap()が使える環境ではそちらを使います。collect
_.reduce(list, iterator, memo, [context])listの各要素に対してiteratorを実行し累積結果を返します。iteratorの戻り値は累積結果であり、次の要素に対してiteratorの呼び出しで引数として渡されます。iteratorの引数は(memo, value)になります。ネイティブの機能でreduce()が使える環境ではそちらを使います。inject, foldl
_.reduceRight(list, iterator, memo, [context])_.reduce()と同等の処理をlistの最後から実行します。ネイティブの機能でreduceRight()が使える環境ではそちらを使います。foldr
_.find(list, iterator, [context])listの各要素に対してiteratorを実行し、戻り値が最初に真になる値を返します。detect
_.filter(list, iterator, [context])listの各要素に対してiteratorを実行し、戻り値が真になる値を抜き出した配列を返します。ネイティブの機能でfilter()が使える環境ではそちらを使います。
_.where(list, properties)listの各要素に対してiteratorを実行し、properties(オブジェクトのkeyとvalue)と合致するものを抜き出した配列を返します。
_.findWhere(list, properties)listの各要素に対してiteratorを実行し、properties(オブジェクトのkeyとvalue)と最初に合致するものを返します。
_.reject(list, iterator, [context])listの各要素に対してiteratorを実行し、戻り値が偽になる値を抜き出した配列を返します。
_.every(list, [iterator], [context])listの各要素に対してiteratorを実行し、戻り値が全て真なら真を、1つでも偽なら偽を返します。ネイティブの機能でevery()が使える環境ではそちらを使います。all
_.some(list, [iterator], [context])listの各要素に対してiteratorを実行し、戻り値が1つでも真なら真を、全て偽なら偽を返します。ネイティブの機能でsome()が使える環境ではそちらを使います。any
_.contains(list, value)listにvalueが含まれていれば真を返します。include
_.invoke(list, methodName, [*arguments])listの各要素に対してmethodNameというメソッドを実行した新しい配列を返します。追加されたargumentsはメソッドの実行時に渡されます。
_.pluck(list, propertyName)listに各要素にpropertyにpropertyNameで指定した値が含まれた新しい配列を返します。_.map()のよく用いられる使われ方です。
_.max(list, [iterator], [context])listの各要素の最大値を返します。iteratorが指定されるとlistに各要素にiteratorを実行し、最大値を返します。
_.min(list, [iterator], [context])listの各要素の最小値を返します。iteratorが指定されるとlistに各要素にiteratorを実行し、最小値を返します。
_.sortBy(list, iterator, [context])listの各要素を、iteratorが指定されるとlistに各要素にiteratorを実行しその値で、文字列でしたらlistの各要素のpropertyの値と同じ文字列のものでソートします。
_.groupBy(list, iterator, [context])listの各要素を、iteratorが指定されるとlistに各要素にiteratorを実行しその値で、文字列でしたらlistの各要素のpropertyの値と同じ文字列のもので分類します。
_.indexBy(list, iterator, [context])listの各要素を、iteratorが指定されるとlistに各要素にiteratorを実行しその値で、文字列でしたらlistの各要素のpropertyの値と同じ文字列のものの値をキーにしたlistを返します。
_.countBy(list, iterator, [context])listの各要素を、iteratorが指定されるとlistに各要素にiteratorを実行しその値で、文字列でしたらlistの各要素のpropertyの値と同じ文字列のもので分類した数を返します。結果の各要素の値は、_.groupBy()の結果の各要素のlengthと同じ値になります。
_.sortedIndex(list, value, [iterator], [context])ソートされたlistに対してvalueの挿入する際のインデックスを返します。
_.shuffle(list)listの各要素を、ランダムに並び替えて返します。
_.sample(list, [n])listの各要素を、ランダムに抜き出して返します。nで返す要素数を指定できます。
_.toArray(list)listを配列として返します。argumentsオブジェクトにたいてい使われます。

 配列の機能
_.first(array, [n])配列の最初の要素を返します。nで返す要素の数を指定できます。head, take
_.initial(array, [n])配列の最後の要素を除いた配列を返します。nで除く要素の数を指定できます。
_.last(array, [n])配列の最後の要素を返します。nで返す要素の数を指定できます。
_.rest(array, [index])配列の最初の要素を除いた配列を返します。indexで除く要素の数を指定できます。tail, drop
_.compact(array)配列から偽となる要素を除いた配列を返します。
_.flatten(array, [shallow])入れ子になった配列をフラットにします。shallowが真の場合、配列の一段目のみフラットにします。
_.without(array, [*values])配列からvaluesを除いた配列を返します。
_.union(*arrays)渡された複数の配列の和集合を返します。
_.intersection(*arrays)渡された複数の配列の積集合を返します。
_.difference(array, *others)配列から配列(others)を除いた配列を返します。
_.uniq(array, [isSorted], [iterator])配列の要素がユニークなもののみ配列にして返します。
_.zip(*arrays)渡された複数の配列を同じインデックスの要素にまとめた配列を返します。
_.object(list, [values])配列をオブジェクトに変換して返します。 [キー、値]のペアの単一のリスト、またはキーのリストと値のリストを渡します。重複したキーが存在する場合は、最後の値が優先されます。
_.indexOf(array, value, [isSorted])配列の要素の中にvalueが存在すればその最初のインデックスを返します。配列の要素の中に存在しない場合は'-1'を返します。ネイティブの機能でindexOf()が使える環境ではそちらを使います。
_.lastIndexOf(array, value, [fromIndex])配列の要素の中にvalueが存在すればその最後のインデックスを返します。配列の要素の中に存在しない場合は'-1'を返します。ネイティブの機能でlastIndexOf()が使える環境ではそちらを使います。
_.range([start], stop, [step])整数の配列を返します。_.map()、_.each()でリストを作成するに便利です。デフォルト値を省略した場合 [start]が0、stepが1になります。

 オブジェクトの機能
メソッド名説明Alias
_.keys(object)オブジェクトのプロパティ名をすべてを返します。
_.values(object)オブジェクトのプロパティ値のすべてを返します。
_.pairs(object)オブジェクトのプロパティ名とプロパティ値を配列のペアにして返します。
_.invert(object)オブジェクトのプロパティ名をプロパティ値に、プロパティ値をプロパティ名にしたオブジェクトのコピーを返します。これが機能するためには、オブジェクト値がすべてユニークで、シリアライズ可能でなければなりません。
_.functions(object)オブジェクトの関数プロパティをソートされた配列で返します。methods
_.extend(destination, *sources)’sources'オブジェクトのプロパティを'destination'オブジェクトにコピーして'destination'オブジェクトを返します。’sources'オブジェクトに同じプロパティ名を持つ場合は最後のものが上書きされます。
_.pick(object, *keys)オブジェクトからkeysのプロパティ名(whitelist)をもつプロパティを抜き出したオブジェクトのコピーを返します。
_.omit(object, *keys)オブジェクトからkeysのプロパティ名(blacklist)をもつプロパティを除いたオブジェクトのコピーを返します。
_.defaults(object, *defaults)オブジェクトのプロパティで⁠⁠ defaults⁠オブジェクトのプロパティで未定義のものがあれば上書きしてオブジェクトを返します。
_.clone(object)オブジェクトの浅い (shallow) コピーされたクローンを作成します。ネストされたオブジェクトや配列は、参照によってコピーされ重複しません。
_.tap(object, interceptor)オブジェクトをinterceptorに渡しオブジェクトを返します。_.chainの中間結果に対して実行をする際に主に使われます。
_.has(object, key)オブジェクトにkeyのプロパティ値のオブジェクトがあるか判定します。hasOwnPropertyのショートカットになりますがより安全な参照を行っています。
_.isEqual(object, other)二つのオブジェクトが等しいか深い比較を行い判定します。
_.isEmpty(object)オブジェクトが要素を持っているか判定します。
_.isArray(object)オブジェクトが配列かどうか判定します。
_.isObject(value)渡された値がオブジェクトかどうか判定します。
_.isArguments(object)Argumentオブジェクトか判定します。
_.isFunction(object)関数か判定します。
_.isString(object)文字列か判定します。
_.isNumber(object)数字(NaN含みます)か判定します。
_.isFinite(object)有限数か判定します。
_.isBoolean(object)bool値か判定します。
_.isDate(object)日付オブジェクトか判定します。
_.isRegExp(object)正規表現オブジェクトか判定します。
_.isNaN(object)NaNか判定します。ネイティブのisNaN()はundefinedでも真を返しますが、こちらは偽で判定します。
_.isNull(object)nullか判定します。
_.isUndefined(value)undefinedか判定します。

 関数の機能
メソッド名説明Alias
_.bind(function, object, [*arguments])指定したオブジェクトのコンテキストで関数を束縛(bind)した新しい関数を返します。引数も部分適用(partial application)する事ができます。
_.bindAll(object, *methodNames)オブジェクトの指定したメソッドをオブジェクトのコンテキストで束縛(bind)します。methodNamesを指定しない場合、全てのメソッドが対象です。
_.partial(function, [*arguments])関数に引数の部分適用(partial application)を処理することが出来るカリー化された新しい関数を返します。
_.memoize(function, [hashFunction])引数に渡した関数を結果を??メモ化します。
_.delay(function, wait, [*arguments])引数に渡したミリ秒後に(setTimeoutのように)関数を呼び出します。引数が渡せます。
_.defer(function, [*arguments])引数に渡した関数を現在実行中の処理の最後まで延期して実行します。
_.throttle(function, wait, [options])引数に渡したミリ秒の時間内には一度しか実行しない関数を返します。
_.debounce(function, wait, [immediate])引数に渡したミリ秒経過するまで実行しない関数を返します。
_.once(function)一度だけ実行される関数を返します。二度目以降は実行せず一度目の結果を返します。
_.after(count, function)引数に渡した関数を指定したcountの回数以上実行した場合、実行する関数を返します。
_.wrap(function, wrapper)第一引数の関数を第二引数の関数に渡して実行する関数を返します。
_.compose(*functions)渡した関数のリストを順番に実行して、その結果を次の関数の引数とする関数を返します。

 その他の機能
メソッド名説明Alias
_.noConflict()すでにUnderscore.jsが使用されている場合、既存のものに⁠_⁠変数に渡します。Underscorej.sのオブジェクトを返します。
_.identity(value)引数をそのまま返します。デフォルトのイテレータとしてUnderscore.jsで使われています。
_.times(n, iterator, [context])iteratorをn回実行します。引数にindexを渡します。
_.random(min, max)minとmaxの間のランダムな整数を返します。引数が1つに場合、0からその番号の間の整数を返します。
_.mixin(object)Underscore.jsを拡張できます。
_.uniqueId([prefix])ユニークなIDを生成します。prefixが渡された場合、IDに追加されます。クライアント側のモデルや唯一のDOM要素のために便利です。
_.escape(string)HTMLに挿入するために、文字列&, , ", 'の文字をエスケープします。
_.unescape(string)_.escapeの逆になります。&, <, >, ", 'を置き換えます。
_.result(object, property)objectのpropertyの値が関数である場合、実行して返します、そうでなければ、値を返します。
_.template(templateString, [data], [settings])引数が1つの場合、テンプレートを引数で置き換える関数を返します。第二引数も指定した場合、第二引数を用いてテンプレートを置き換えた文字列を返します。

まとめ

Underscore.jsの機能を一通りご紹介いたしました。

コレクションの処理など便利な機能、関数型プログラミングをはじめたいと考えている方でしたら良いきっかけになるかと思います。

制作者のJeremy Ashkenasは、twitterの自己紹介文で、

"Code is Literature(ソースコードは文学。)"

と記してます。

画像

Underscore.jsはそのままそれが実践されておりソースコードもわかりやすく楽しく読め、個人的にも非常に勉強になりました。

公式サイトでは影響を受けたと思われる、関数や記事なども紹介されています。ちなみに、Underscore.jsのtemplateでのJavaScriptのwithの使い方などは、John Resig(jQueryの開発者)JavaScript Micro-Templatingの記事から影響されたようです。

3回にわたり、⁠Underscore.jsの入り口」につきましてこれで終了になります。短い間でしたがおつきあいありがとうございました。

おすすめ記事

記事・ニュース一覧