Anything New

developping any time

lodash(Underscore.js)まとめ, Swift, Objective-C, PHPもあるよ

lodash(Underscore.js)

JavaScriptのユーティリティライブラリ。
2015年5月現在、npmでもっとも多く参照されている。
https://www.npmjs.com/browse/depended
lodashはUnderscoreの派生ライブラリ。
Backbone.jsは、もともとunderscore.jsに依存していますし、
lodashは、JavaScriptトランスパイラのBabel,ブログプラットフォームのGhost,
プロジェクトのベースとなるツールであるYeomanなど,npmの主要なパッケージのいくつかは依存している。
AngularJsでDI(依存性注入)してlodash(underscore.js)をモジュール化して、そのまま使用してもいい。
Reactでももちろん使用可能。

lodash(Underscore.js)を使用する目的

配列やオブジェクトの操作をする際に便利
コードの可読性アップ
つまりコードをシンプルかつ短縮できる
for文、if文を多用せずに済む
つまり保守性が高い(変数の状態を知らなくて済む, 関数は参照透過性が保たれている)

Underscoreとlodashの比較

Lo-Dashの方が機能数が多い
Lo-Dashの方が高速、パフォーマンスがいい
native vs. array.js vs. underscore vs lo-dash
http://jsperf.com/native-vs-array-js-vs-underscore/8

lodash(Underscore)便利メソッド

// latest 3.7.0
var _ = require('lodash');

/**
* lodash(Underscore.js) の便利な関数ライブラリたち
* 「_」というオブジェクトを使用することにより利用可能
* 配列、オブジェクト操作を便利にするもの
*
* よく使うのは以下
* each
* find
* filter
* where
* findWhere
* contains
* pluck
* max
* min
* sortBy
*/

Collection functions

each

/**
* ループ処理 collection each
* => each: 4
* => each: 10
* => each: 16
*/
_.each([2, 5, 8], function(num){
console.log( 'each: ' + num * 2 );
});

for と mapの例

/**
* jsのfor文で配列に要素を追加
* => count_arr:2,4,6,8,10
*/
var count_arr = [];
for(var i = 1; i < 6; i++){
count_arr.push(i * 2); // マッピングのたびに外部の変数の状態が変化する ☓
}
console.log('count_arr:' + count_arr);

/**
* mapで配列を操作
* 返り値は配列
* => map: 2,4,6,8,10
*/
var map = _.map(_.range(1, 6), function(num){
return num * 2; // 変数の状態を考えずに済む ○
});
console.log( 'map: ' + map );

for と reduce × filterの例

/**
* 文字列配列の内、三文字以上のものを選択し、
* 文字数の合計を集計する
* => 18
*/
var cities = ["京都", "サンクトペテルブルク", "ボゴダ", "東京", "ゴア", "バルセロナ"];
var total_str_len = 0;
for(var city in cities){
var str_len = cities[city].length;
if(str_len >= 3){
total_str_len += str_len; // マッピングのたびに外部の変数の状態が変化する ☓
}
}
console.log( 'for total_str_len:' + total_str_len); // => 18


/**
* 上記をreduce × filter でやる
* reduceの返り値は単一の値
* filterにて、条件で絞った配列に対して, reduceで一つずつの要素に対して処理をする。
* memoに結果がストックされる。第三引数0はmemoの初期値
* => 18
*/
var total_len = _.reduce(
_.filter(cities,function(city){return city.length >= 3;}),
function(memo, str){ return memo + str.length;}, 0);
console.log( 'function total_len:' + total_len); // 18

chain

/**
* chain メソッドチェーンで使用
* value()でchain結果を取得
* => 18
*/
var total = _.chain(cities)
.filter(function(city){ return city.length >= 3 })
.reduce(function(memo, str){ return memo + str.length;}, 0)
.value();
console.log( 'function total:' + total); // 18

flatMapを使用したい場合(カスタム)

/**
* flatMapを使用したい場合
*/
var flatMap = _.flatMap = _.concatMap = function (xs, f, self) {
return _.reduce(xs, function (ys, x) {
return ys.concat(f.call(self, x));
}, []);
};
_.each(['flatMap', 'concatMap'], function (name) {
_.prototype[name] = function (f, self) {
return _(flatMap(this.value(), f, self));
};
});


/**
* 以下、
* 整数リストのリスト中から奇数だけを取り出して並べたリストを作成
*/

// 普通にやるとこう filter ✕ map
var filterMap = _.filter(_.range(1, 11), function(x){
return x % 2 === 1;
}).map(function(x){
return x;
});
console.dir( 'filterMap:' + JSON.stringify(filterMap, null, 4));

// filter ✕ reduce
var filterReduce = _.filter(_.range(1, 11), function(x){
return x % 2 === 1;
}).reduce(function(flattened, other){
return flattened.concat(other);
},[]);
console.dir( 'filterReduce:' + JSON.stringify(filterReduce, null, 4));

// flatMap使用
var flatMap = _.flatMap(_.range(1, 11), function (x) {
return x % 2 === 1 ? [x] : [];
});
console.dir( 'flatMap:' + JSON.stringify(flatMap, null, 4));

// 同等
var concatMap = _.concatMap(_.range(1, 11), function (x) {
return x % 2 === 1 ? [x] : [];
});
console.dir( 'concatMap:' + JSON.stringify(concatMap, null, 4));

lodashにはtransformがあった!

/**
* transform
* lodashにはtransformがあった!
* Underscore.jsのreduceの代替
* 結果は上記と同じ
*/
var transform_arr = _.transform(_.range(1, 11), function(result, x, i) {
return x % 2 === 1 ? result.push(x): [];
});
console.dir( 'transform:' + JSON.stringify(transform_arr, null, 4));

find

var a = [1, 3, 6, 9];

/**
* find
* 集合要素に対しての操作
* 一番目の要素を探して返す
* => find: 6
*/
console.log( 'find: ' + _.find(a, function(num){
return num > 5; // 6
}));

where

/**
* where
* 条件に合致するオブジェクトを返す
*/
console.log( 'where:' + JSON.stringify(
_.where(PlayList, {artist : "Pharrell Williams"}), null, 4)
);

findWhere

/**
* findWhere
* 条件に合致する最初のオブジェクトを返す
*/
console.log( 'findWhere:' +
JSON.stringify(
_.findWhere(PlayList, {artist : "Pharrell Williams"}), null, 4
)
);

filter

/**
* filter
* 条件に一致したものを配列で返す
* => filter: 6,9
*/
z = _.filter(a, function(num){
return num > 5;
});
console.log( 'filter: ' + z ); // 6, 9

contains

/**
* contains
* 配列内に指定値があるか => true or false
* => contains: false
*/
console.log( 'contains: ' + _.contains(a, 10));

groupBy

/**
* groupBy
* 結果によるグループ化
* groupBy: [object Object]
*/
var groupResult = _.groupBy([1, 2, 5, 8, 42, 12], function(num){
return num % 3;
});
console.dir( 'groupBy:' + JSON.stringify(groupResult, null, 4));

countBy

/**
* countBy
* グループの要素カウント
* => Object {odd: 2, even: 4}
*/
var countResult = _.countBy([1, 2, 5, 8, 42, 12], function(num){
return num % 2 == 0 ? 'even' : 'odd';
});
// => Object {odd: 2, even: 4}
console.dir( 'countResult:' + JSON.stringify(countResult, null, 4));

sortBy

/**
* sortBy
* => sortBy Math sin: 5,42,12,1,2,8
*/
console.log( 'sortBy Math sin: ' + _.sortBy([1, 2, 5, 8, 42, 12], function(num){
return Math.sin(num);
}) );

/**
* sortBy
* => sortBy length: i,me,and,you,dog,father
*/
console.log( 'sortBy length: ' + _.sortBy(['i', 'me', 'and', 'you', 'father', 'dog'], 'length') );

union

var c = [1, 2, 5];
var d = [2, 4, 6];

/**
* union
* 配列の結合(ユニーク)
* => union: 1,2,5,4,6
*/
console.log( 'union: ' + _.union(c, d) );

intersection

/**
* intersection
* 配列の共通箇所を抽出
* => intersection: 2
*/
console.log( 'intersection: ' + _.intersection(c, d) );

difference

/**
* difference
* 第一引数の配列を元に、比較して違いを配列で返す
* => difference: 1,5
*/
console.log( 'difference: ' + _.difference(c, d) );

uniq

/**
* uniq
* => uniq: 2,5,10
*/
console.log( 'uniq: ' + _.uniq([2,5,10,2, 5]) );

template

/**
* template
* テンプレート機能 テンプレートを引数で置換
* <% %> js evaluate
* <%= %> interpolate
* <%- %> escape output
*/
var js_ = "<% console.log('hello from tmpl'); %>";
var html_tpl = "
  • <%- name %> - (<%- age %>)

  • "
    ;
    console.log(_.template(
    "Using 'with': <%= data.answer %>",
    {variable: 'data'})({answer: 'no'})); // => Using 'with': no

    Object function

    keys

    /**
    * オブジェクト操作
    * @type {{name: string, age: number, url: string}}
    */
    var user = {
    name: 'matsuoka',
    age : 27,
    url : 'http:manchan.github.io'
    };

    // => keys: name,age,url
    console.log( 'keys: ' + _.keys(user) );

    values

    // => values: matsuoka,27,http:manchan.github.io
    console.dir( 'values: ' + _.values(user) );

    invert

    // => Object {27: "age", matsuoka: "name", http:manchan.github.io: "url"}
    console.dir( 'invert:' + JSON.stringify(_.invert(user), null, 4) );

    extend 参照先もコピーされるため,参照元の値を上書きするおそれがある ※ディープクローンしたい場合は、cloneDeep

    /**
    * extend
    * _.extend(destination, *sources)
    * soucesオブジェクトのすべてのプロパティをdestinationオブジェクトにコピーする。
    * sourcesの中に同じ名前のプロパティが含まれていた場合、より後のもので上書きされる。
    */
    // => {name : 'matsuoka', age : 27}
    console.dir( 'extend:' + JSON.stringify(_.extend({name : 'matsuoka'}, {age : 27}), null, 4));

    var firstObj = {firstObj: 1},
    secondObj;
    secondObj = _.extend(firstObj, {secondObj: 2});
    console.log(firstObj); // {firstObj: 1, secondObj: 2}
    console.log(secondObj); // {firstObj: 1, secondObj: 2}
    secondObj.firstObj = 3;
    console.log(firstObj); // {firstObj:3, secondObj:2} !上書きされている
    console.log(secondObj); // {firstObj:3, secondObj:2}

    defaults

    /**
    * defaults
    * _.defaults(object, *defaults)
    * objectがdefaultsオブジェクトのプロパティを持っていない場合、
    * objectにそのdefaultsオブジェクトのプロパティを付与する。
    */
    var iceCream = {flavor : "chocolate"};
    // => {flavor : "chocolate", sprinkles : "lots"}
    console.dir( 'defaults:' + JSON.stringify(
    _.defaults(iceCream, {flavor : "vanilla", sprinkles : "lots"}),
    null,
    4));

    clone (shallow copy

    /**
    * clone
    * _.clone(object)
    * objectのシャローコピーを作る。
    * ネストされたオブジェクトや配列は、参照コピーされる
    */
    var clone_obj = _.clone({name : 'matsuoka'});
    // => {name : 'matsuoka'}
    console.dir( 'clone:' + JSON.stringify(clone_obj, null, 4));

    has

    // => has: true
    console.log( 'has: ' + _.has(user, "name"));
    // => false
    if( !_.has(clone_obj, 'foo') ){
    // error
    // return;
    }

    is系(isUndefined, isEmpty, isStringなど)

    /**
    * is系は多い
    * => true or false
    */
    // => isEmpty user.name: false
    console.log( 'isEmpty user.name: ' + _.isEmpty(user.name));
    // => isNull user.age: false
    console.log( 'isNull user.age: ' + _.isNull(user.age));
    // => isString user.age: false
    console.log( 'isString user.age: ' + _.isNull(user.age));
    // => isNumber user.age: true
    console.log( 'isNumber user.age: ' + _.isNumber(user.age));

    shuffle

    /**
    * 関数的シャッフル
    * => shuffle1: 3,8,2,4
    */
    console.log( 'shuffle1: ' + _.shuffle([2,3,8,4]) );
    // OOP
    console.log( 'shuffle2: ' + _([2,8,10,3]).shuffle() );

    range

    // => range 1,10: 1,2,3,4,5,6,7,8,9
    console.log( 'range 1,10: ' + _.range(1, 10) );
    // => range 0,10: 0,1,2,3,4,5,6,7,8,9
    console.log( 'range 0,10: ' + _.range(10) );
    // => range 1,11,2: 1,3,5,7,9
    console.log( 'range 1,11,2: ' + _.range(1, 11, 2) );

    random

    // => random 0, 10: 2
    console.log( 'random 0, 10: ' + _.random(10) );

    escape

    // => escape: