この回答を書くことに触発されて、私は後でこれを詳細に説明するブログ投稿を拡張して書くことになりました. この問題についての考え方をより深く理解したい場合は、これを確認することをお勧めします。1 つずつ説明し、最後に JSperf の比較を行い、速度に関する考慮事項について説明します。
つまり、**tl;dr は次のとおりです。
求めていること (1 つの関数呼び出し内でのフィルタリングとマッピング) を達成するには、Array.reduce()
** を使用します。
ただし、より読みやすく (それほど重要ではありませんが)通常は大幅に高速な2のアプローチは、フィルターとマップを連結して使用することです。
[1,2,3].filter(num => num > 2).map(num => num * 2)
以下は、どのようにArray.reduce()
動作するか、および 1 回の反復でフィルターとマップを実行するために使用する方法について説明しています。繰り返しになりますが、これがあまりにも凝縮されている場合は、上記のリンク先のブログ投稿を参照することを強くお勧めします。
reduce に (通常は無名の) 関数である引数を与えます。
その無名関数は 2 つのパラメーターを取ります。1 つは (map/filter/forEach に渡される無名関数のように) 操作対象の iteratee です。reduce に渡される無名関数には別の引数がありますが、それらの関数は受け入れません。これは関数呼び出し間で渡される値であり、多くの場合memoと呼ばれます。
Array.filter() は 1 つの引数 (関数) しか取りませんが、Array.reduce() は重要な (オプションですが) 2 番目の引数も取ることに注意してください。最初の引数であり、その後、変更して関数呼び出し間で渡すことができます。(指定されていない場合、最初の無名関数呼び出しの 'memo' はデフォルトで最初の iteratee になり、'iteratee' 引数は実際には配列の 2 番目の値になります)
この場合、最初に空の配列を渡し、関数に基づいて iteratee を配列に挿入するかどうかを選択します。これがフィルタリング プロセスです。
最後に、各匿名関数呼び出しで「進行中の配列」を返し、reduce はその戻り値を受け取り、次の関数呼び出しに引数 (memo と呼ばれます) として渡します。
これにより、フィルターとマップを 1 回の反復で実行できるため、必要な反復回数が半分に削減されます。ただし、反復ごとに 2 倍の作業を行うだけなので、JavaScript ではそれほど高価ではない関数呼び出し以外には何も保存されません。 .
より完全な説明については、MDNドキュメント (またはこの回答の冒頭で参照されている私の投稿) を参照してください。
Reduce 呼び出しの基本的な例:
let array = [1,2,3];
const initialMemo = [];
array = array.reduce((memo, iteratee) => {
// if condition is our filter
if (iteratee > 1) {
// what happens inside the filter is the map
memo.push(iteratee * 2);
}
// this return value will be passed in as the 'memo' argument
// to the next call of this function, and this function will have
// every element passed into it at some point.
return memo;
}, initialMemo)
console.log(array) // [4,6], equivalent to [(2 * 2), (3 * 2)]
より簡潔なバージョン:
[1,2,3].reduce((memo, value) => value > 1 ? memo.concat(value * 2) : memo, [])
最初の iteratee が 1 より大きくなかったので、フィルター処理されたことに注意してください。また、その存在を明確にし、注意を引くために名前が付けられた initialMemo にも注意してください。もう一度、最初の無名関数呼び出しに 'memo' として渡され、次に無名関数の戻り値が 'memo' 引数として次の関数に渡されます。
memo の古典的な使用例のもう 1 つの例は、配列内の最小または最大の数値を返すことです。例:
[7,4,1,99,57,2,1,100].reduce((memo, val) => memo > val ? memo : val)
// ^this would return the largest number in the list.
独自の reduce 関数を作成する方法の例 (これは、これらのような関数を理解するのに役立つことがよくあります):
test_arr = [];
// we accept an anonymous function, and an optional 'initial memo' value.
test_arr.my_reducer = function(reduceFunc, initialMemo) {
// if we did not pass in a second argument, then our first memo value
// will be whatever is in index zero. (Otherwise, it will
// be that second argument.)
const initialMemoIsIndexZero = arguments.length < 2;
// here we use that logic to set the memo value accordingly.
let memo = initialMemoIsIndexZero ? this[0] : initialMemo;
// here we use that same boolean to decide whether the first
// value we pass in as iteratee is either the first or second
// element
const initialIteratee = initialMemoIsIndexZero ? 1 : 0;
for (var i = initialIteratee; i < this.length; i++) {
// memo is either the argument passed in above, or the
// first item in the list. initialIteratee is either the
// first item in the list, or the second item in the list.
memo = reduceFunc(memo, this[i]);
// or, more technically complete, give access to base array
// and index to the reducer as well:
// memo = reduceFunc(memo, this[i], i, this);
}
// after we've compressed the array into a single value,
// we return it.
return memo;
}
実際の実装では、たとえばインデックスなどにアクセスできますが、これがその要点を簡単に理解するのに役立つことを願っています.