Aqutras Members' Blog

株式会社アキュトラスのメンバーが、技術情報などを楽しく書いています。

Array.prototype.forEach ではなく _.forEach を使う理由

こんにちは.maxmellon です.

この記事では,javascript のプログラムにおいて,ブラウザが forEach に 対応したにもかかわらず、一部のプロジェクトにおいて, lodash や underscore などといったライブラリをわざわざ読み込んで _.forEach などの関数をつかう理由について迫りたいと思います.

余談ですが,Aqutras でも 一部 プロジェクトに lodash が導入されました.

underscore, lodashとは

underscore, lodash ともに,javascript を開発する上で,比較的よく使われるような 汎用的な関数を集合させたライブラリです.具体的には,map (配列のすべての要素に対して,何らかの操作を行い,その結果から新しい配列を作成) や forEach (配列の要素を前から順番に繰り返し参照),join (配列のすべての要素を一つの文字列として合成) などがあります. IE の対応をするときは,_.isNaN (数字であるかそうでないかの判定) とかも便利だと思います.(IE の一定のバージョン以下には,window.isNaN がないため) あとは,javascript では,Array も typeof すると Object になってしまうので, _.isArray などが便利ですね.

underscore の 詳細や使用例はこのページが分かりやすかったです. 詳細はこちらで確認していただけると幸いです.

http://gihyo.jp/dev/serial/01/underscorejs/0001

lodashは,underscore をさらにモダンにしようと作られたものです. 大半の関数が underscore と互換性をもっています.

なぜ underscore, lodash を用いるのか

理由は,単純に直感的かつ便利な関数が多いからです.

例えば,長さ100 の 1次元配列を 3つ区切りの二次元配列にしたい時, 素のjavascript だと2重ループを自分で書いて関数を定義する必要がありますが, underscore, lodash を用いれば,これを1行で書き上げることができます.

_.chunk(array, 3);

今回の例は,ごくごく一例でこのような便利関数が非常に沢山盛り込まれています. 自分で1から同じものを作るより,すでにあるものを使うほうが開発速度はあがります。 更に,lodashやunderscoreは多くの人が利用しているので,バグが存在する可能性が低く バグが存在したとしても,すぐにpull request が発行され修正されることが多いです. なので,javascript を開発するときは,入れておいて損はないライブラリです.

さらには,プロジェクトが異なっても全く同じ要領で関数を扱うことができるという点が挙がります. 汎用関数を自作した場合,プロジェクトが異なると,その汎用関数をコピペしないと使うことができません.対して,underscore や lodash の場合,ライブラリを読み込むことだけでよいです.document もしっかりしたものがすでにあります.

なぜ,もともとの js にもある forEach ではなく _.forEach をつかうのか

forEach に限らず,一部の関数(例えば,mapfilter)が,すでにjavascript に 実装されているにもかかわらず, underscore, lodash の関数が使われている事があります. その理由について触れたいと思います.

結論から言いますと,標準関数に比べて,underscore , lodash のほうが 大きくパフォーマンスが優れています. 具体的にどれほどにパフォーマンスが異なるのかを, ベンチマークのコードと結果を合わせて触れたいと思います.

var U = require('underscore');
var L = require('lodash');
var Benchmark = require('benchmark');
var suite = new Benchmark.Suite();

var array = [];
Benchmark.prototype.setup(function() {
  array = [1000];
});

suite
  .add('prototype forEach', function () {
    var a = 0;
    array.forEach(function(i) {
      a += i;
    });
  })
  .add('undercore forEach', function () {
    var b = 0;
    U.forEach(array, function(i) {
      b += i;
    });
  })
  .add('lodash forEach', function () {
    var c = 0;
    L.forEach(array, function(i) {
      c += i;
    });
  })
  .on('cycle', function(event, bench) {
    console.log(String(event.target));
  })
  .on('complete', function() {
    console.log('\nFastest is ' + this.filter('fastest').map('name'));
  })
  .run({async: false});

上記のベンチマークの実行結果は次のようになります.

forEach

prototype forEach x 17,492,421 ops/sec ±1.54% (85 runs sampled)
undercore forEach x 14,420,068 ops/sec ±1.67% (85 runs sampled)
lodash forEach x 20,215,636 ops/sec ±1.80% (81 runs sampled)

Fastest is lodash filter

ops/sec は 1秒間のオペレーション数です つまり,lodash が 一番高速であるということがわかります. forEach に限っては,このような結果になりましたが,map はどうでしょうか.

map

prototype map x 4,830,952 ops/sec ±1.32% (85 runs sampled)
undercore map x 16,144,965 ops/sec ±1.75% (84 runs sampled)
lodash map x 25,058,873 ops/sec ±2.19% (87 runs sampled)

Fastest is lodash map

map においては, underscore が 4倍ほど,lodashが5倍ほど,1秒間のオペレーション数が 速いという結果になりました.

filter

prototype filter x 4,863,005 ops/sec ±1.69% (82 runs sampled)
undercore filter x 7,595,434 ops/sec ±1.76% (82 runs sampled)
lodash filter x 15,287,401 ops/sec ±9.16% (68 runs sampled)

Fastest is lodash filter

filter も lodash が 速いという結果になりました。

find

prototype find x 20,086,683 ops/sec ±3.26% (83 runs sampled)
undercore find x 11,694,520 ops/sec ±2.49% (83 runs sampled)
lodash find x 16,371,902 ops/sec ±9.03% (67 runs sampled)

Fastest is prototype find

find に関しては標準関数が一番大きいという結果になりました。 これは,underscore lodash の find が array だけではなく オブジェクトにも対応しているからです.

fill

prototype fill x 26,988,775 ops/sec ±1.96% (84 runs sampled)
lodash fill x 55,914,844 ops/sec ±2.85% (82 runs sampled)

Fastest is lodash fill

underscore に fill はありません. これもlodashが2倍ほど速いですね.

join

prototype join x 26,103,804 ops/sec ±3.10% (78 runs sampled)
lodash join x 24,622,303 ops/sec ±1.66% (81 runs sampled)

Fastest is prototype join

underscore に join はありません. これは,lodashのほうが遅かったですね.(差は大きくありませんが)

underscore より lodash を選択する理由

上のベンチマークの結果を見てもらえればわかるように,ほとんどがlodashが パフォーマンスが上回っているという結果になりました. パフォーマンスと言う観点から考えると,lodash のほうが採用しやすいでしょう.

lodash には,もう一つ大きなメリットがあります. それは,lodash の 一部関数だけ使いたい時,その関数だけを読み込むことができます.

underscore の場合,例えば find だけ使いたいというケースがあっても, ライブラリのすべてを読み込む必要があります.

対して,lodashは 各関数ごとにライブラリ化してあるため,関数一つだけを読み込むという事が可能です. 具体的には,

$ npm install lodash.find

とすることで,lodashのfind だけを使うことができます.

まとめ

  • パフォーマンスと言う観点から Array.prototype.forEach より,_.forEach を使うべきである.
  • 一部を除いて,基本的にlodashが高速である.
  • 標準関数より遅いものも,大差ではない.
  • すべてを読み込む事ができなくても,使いたいものだけが使える.

lodash を使って幸せなjavascript ライフを送りましょう.