先日ヘルシオホットクックを衝動買いして、あまりの便利さにメルトダウン中です。
みんなも買おうヘルシオホットクック。
「ヘルシオホットクックでカレー作った」をトゥギャりました。 https://t.co/eWXmMOPIfL
— ラナ・クアール (@rana_kualu) 2019年1月3日
以下はpacdivによる記事、Here’s how you can make better use of JavaScript arraysの日本語訳です。
Here’s how you can make better use of JavaScript arrays
この記事はさっくりと読めます。本当だよ。
この数ヶ月の間に、私がチェックしているリポジトリで4種類のプルリクエストがやってきていることに気付きました。
これらの間違いは全て自分で作っていたところだったので、この記事を書いています。
配列メソッドを正しく使うようにしましょう。
Replacing Array.indexOf with Array.includes
Array.indexOf
ではなくArray.includes
を使う。
『Arrayから探したいものがあるならArray.indexOf
を使おう』
JavaScriptを学んでいたとき、何かのコースにこのような文章が書いてありました。
このセンテンスは全くもって正しいです。
MDNのドキュメントには、最初に見つけた要素のインデックスを返すと書かれています。
従って、そのインデックスを後で使うのであればArray.indexOf
が正解です。
しかし、単に配列に値が含まれているか否かだけを知りたいだけであるときはどうでしょうか。
含まれているかを知りたいだけならbool値で十分です。
この場合はbooleanを返すArray.includes
を使った方がいいです。
'use strict';
const characters = [
'ironman',
'black_widow',
'hulk',
'captain_america',
'hulk',
'thor',
];
console.log(characters.indexOf('hulk')); // 2
console.log(characters.indexOf('batman')); // -1
console.log(characters.includes('hulk')); // true
console.log(characters.includes('batman')); // false
Using Array.find instead of Array.filter
Array.filter
ではなくArray.find
を使う。
Array.filter
は非常に便利なメソッドです。
ある配列にコールバックを渡し、それを通過した値だけを集めた新たな配列を作ります。
名前のとおり、フィルタリングを行ってより短い配列にすることができます。
しかし、コールバックが必ずひとつしか値を返さないとわかっている場合、たとえばフィルタリングのキーに一意のIDを使う、にはArray.filter
は勧められません。
この場合Array.filter
は長さが1の配列を返しますが、唯一の値を使用したいのですから配列になっている意味は全くありません。
パフォーマンスについても考えてみましょう。
Array.filter
は一致する全ての値を返すために、配列全体を走査します。
さらにコールバックに一致する値がたくさんあったとしたら、フィルタリングされたあとの配列も非常に大きなものとなってしまいます。
そのような状況を避けるため、Array.find
の使用を検討しましょう。
これはArray.filter
と同じようにコールバック関数を受け取り、それを満たす最初の値を返します。
そしてその時点でArray.find
はすぐに終了するため、配列の全体を走査しなくて済みます。
またArray.find
を使うことで、1件だけを取り出したいという意図を明確にすることができます。
'use strict';
const characters = [
{ id: 1, name: 'ironman' },
{ id: 2, name: 'black_widow' },
{ id: 3, name: 'captain_america' },
{ id: 4, name: 'captain_america' },
];
function getCharacter(name) {
return character => character.name === name;
}
console.log(characters.filter(getCharacter('captain_america'))); // 3と4の配列
console.log(characters.find(getCharacter('captain_america'))); // 3だけ
Replacing Array.find with Array.some
Array.find
ではなくArray.some
を使う。
私はこのミスを何度もやってしまったことがあります。
その後友人に、MDNのドキュメントにもっといい方法があると教えてもらいました。
この段落は、Array.indexOf/Array.includes
の場合とよく似ています。
前段では、Array.find
にコールバックを渡し、返り値としてその要素の値を受け取っていました。
これがもし値そのものではなく、値が含まれているか否かを知りたいという場合、Array.find
は最適なソリューションでしょうか。
おそらく違います。返り値はbooleanで十分なはずです。
そのような場合は、booleanを返すArray.some
の使用をお勧めします。
Array.some
を使うと、値そのものについては不要であるという意図が強調されます。
'use strict';
const characters = [
{ id: 1, name: 'ironman', env: 'marvel' },
{ id: 2, name: 'black_widow', env: 'marvel' },
{ id: 3, name: 'wonder_woman', env: 'dc_comics' },
];
function hasCharacterFrom(env) {
return character => character.env === env;
}
console.log(characters.find(hasCharacterFrom('marvel'))); // { id: 1, name: 'ironman', env: 'marvel' }
console.log(characters.some(hasCharacterFrom('marvel'))); // true
Using Array.reduce instead of chaining Array.filter and Array.map
Array.filter
+ Array.map
ではなくArray.reduce
を使う。
たしかにArray.reduce
はわかりにくいです。
しかし、Array.filter
してArray.map
するのは何かこう違う感じがありませんか?
ここでは配列を2回読み込んでいます。
最初の配列をフィルタリングして短い配列を作成し、それを使ってさらにもうひとつの配列を作成しています。
ひとつの配列を得るためだけに、ふたつのメソッドを使ってしまいました。
この無駄によるパフォーマンス低下を避けるため、かわりにArray.reduce
の使用をお勧めします。
結果が同じなら、より良いコードを選びましょう。
Array.reduce
を使うと条件を満たす要素をフィルタリングして第二引数accumulatorに積むことができるようになります。
accumulatorとしてはインクリメント、オブジェクト、文字列、配列などが使えます。
今回の例ではArray.map
を使っていたので、accumulatorには配列を使うことにしましょう。
次の例ではenv
の値に応じて値をaccumulatorに積むか、何もしないかを決めています。
'use strict';
const characters = [
{ name: 'ironman', env: 'marvel' },
{ name: 'black_widow', env: 'marvel' },
{ name: 'wonder_woman', env: 'dc_comics' },
];
console.log(
characters
.filter(character => character.env === 'marvel')
.map(character => Object.assign({}, character, { alsoSeenIn: ['Avengers'] }))
);
// [
// { name: 'ironman', env: 'marvel', alsoSeenIn: ['Avengers'] },
// { name: 'black_widow', env: 'marvel', alsoSeenIn: ['Avengers'] }
// ]
console.log(
characters
.reduce((acc, character) => {
return character.env === 'marvel'
? acc.concat(Object.assign({}, character, { alsoSeenIn: ['Avengers'] }))
: acc;
}, [])
) // filter+mapと全く同じになる
That’s it!
この記事は役に立ちましたか?
意見や他のユースケースがあるなら是非コメントを残してください。
役に立ったようであれば、拍手して、この記事をシェアしてください。
読んでくれてありがとう。
注意:IEではArray.find
とArray.includes
がサポートされてないから、使うときにはサポートしてるバージョンに気をつけよう。
コメント欄
「よい記事。さっそく幾つかを試してみよう。」
「コード分割と再利用の観点から、場合によってはあえてfilter+mapを使う方が優れているだろう。」
「Array.prototype.includes
はES7の機能で、サポートされてないブラウザがあるから注意が必要。」
「パフォーマンスについての主張はおそらく間違っています。確認しましたか? そこ以外は全てグッド。」
「LovelyなIEのためにforでループさせられることを強いられているんだ」
「最後の例はよいとは思えない。かわりにtransducerを使うといいんじゃないか。」
「基本的にはいい記事だけど、みんながコメントしてるようにツッコミどころがいくつもある。次の更新を楽しみにしてるよ。」
「concatしてるせいで毎回新しいオブジェクトを生成してる。そうではなくこうするべき。」
characters .reduce((acc, character) => {
if (character.env === ‘marvel’) {
acc.push(Object.assign({}, character, { alsoSeenIn: [‘Avengers’] }));
}
return acc;
}, [])
感想
『○○のかわりに××が使えるよ』の後に必ず『ただし△△なら』が入ってるので、そのまま機械的に差し替えられるようなものではありません。
役に立つかどうかといえば役立ちますが、7千いいねも得るほどかなあ?という印象。
そもそも配列操作なんて、よっぽど長い配列でもない限りパフォーマンスなんて大して変わらないのだから、他の部分に注力した方がいいんじゃないか。
という長文のマジレスが返ってきていました。
このエントリは参考にとどめて、読みやすさに重きを置いたコーディングをしたほうがいいでしょう。