athome-developer’s blog

不動産情報サービスのアットホームの開発者が発信するブログ

Lodashの2つの書き方について

情報システム部の高野です。
今日は、JavaScriptのライブラリ「Lodash」について書きます。

Lodashは、色々なことができるライブラリなのですが
私は、集合を扱う関数(C#でいうLINQ to Object)しかほぼ使っていません。
この集合を扱う関数で2種類の書き方があります。


※以降で扱うソースコードは、TypeScriptで記述します。


1つ目の書き方は、_メソッドの引数に配列を書く方法です。

_([1,2,3,4]).filter(x => x % 2 == 0).map(x => x * x).forEach(x => console.log(x));

// 4
// 16


2つ目の書き方は、filterなどのメソッドの引数に配列を書く方法です。

_.filter([1,2,3,4], x => x % 2 == 0).map(x => x * x).forEach(x => console.log(x));

// 4
// 16


この2つは、どちらも結果は同じになります。(違ったら困りますね)
では、なぜ2種類の書き方があるのかと言うと
1つ目は、遅延実行されるのです。

どういうことかを確認するため下記のように書き換えます。

_([1,2,3,4]).filter(x => {
    console.log("filter:" + x);
    return x % 2 == 0;
}).map(x => {
    console.log("map:" + x * x);
    return x;
});

これは、なにも出力されません。

_.filter([1,2,3,4], x => {
    console.log("filter:" + x);
    return x % 2 == 0;
}).map(x => {
    console.log("map:" + x * x);
    return x;
});

// filter:1
// filter:2
// filter:3
// filter:4
// map:2
// map:4

こちらは、普通に出力されます。
遅延実行は、必要な時がくるまで実行されないのです。

ではどうやって実行するかですが、下記のように書きます。

var a = _([1,2,3,4]).filter(x => {
    console.log("filter:" + x);
    return x % 2 == 0;
}).map(x => {
    console.log("map:" + x * x);
    return x;
});

a.value(); // LINQのToArray()と同じ

// filter:1
// filter:2
// filter:3
// filter:4
// map:2
// map:4

valueメソッドやforEachメソッド、sizeメソッドなどで処理が実行されます。
ちなみにmapメソッドの後にvalueメソッドを続けて書くことも、もちろんできます。

出力結果を見てC#を知っている方は違和感を持ったのではないでしょうか?
ここでC#で同じことをやってみます。

var sample = new []{1, 2, 3, 4};

sample.Where(x => { // filterは、linqではWhere
    WriteLine($"filter:{x}");
    return x % 2 == 0;
}).Select(x => {    // mapは、linqではSelect
    WriteLine($"map:{x * x}");
    return x;
}).ToArray();

// filter:1
// filter:2
// map:4
// filter:3
// filter:4
// map:16

mapが出てくるタイミングが違いますね。
C#LINQでは、ストリーム的に1つずつ次の処理に投げているのに対して
Lodashの場合は、1つのメソッドの処理を終えてから次のメソッドに投げています。

では、JavaScriptでは、ストリーム的に処理を流すことはできないのか?
linq.jsというライブラリで試してみましょう。

linq.from([1,2,3,4]).where(x => {
    console.log("filter:" + x);
    return x % 2 == 0;
}).select(x => {
    console.log("map:" + x * x);
    return x;
}).toArray();

// filter:1
// filter:2
// map:4
// filter:3
// filter:4
// map:16

C#とまったく同じになります。

それじゃlinq.jsの方が効率も良いし高速なのでは?
計測してみましょう。

このようなプログラムを作成し
Lodashの遅延実行・即時実行、linqjsで比較してみます。

let sample = linq.range(1, 100000).toArray();
for(let i = 0; i < 10; i++)
{
    console.time("timer");

    _(sample).filter(x => x % 2 == 0).map(x => x * x).value();

    console.timeEnd("timer");
}

10回の内の最大値と最小値を抜いた8回の平均です。

  • Lodash遅延実行・・・4.97ms
  • Lodash即時実行・・・26.6ms
  • linqjs・・・44.5ms

予想に反してLodashの遅延実行が一番速かったです。


ということでLodash使うなら基本的には遅延実行の方を使った方が良いでしょう。
でもRemoveメソッドとかは、即時実行の方が良いケースが多いと思うのでちゃんと使い分けるってことですかね。
※10万回ループしての差なので業務システムなどの場合は、どちらを選んでも大差無いと思われます。