JavaScript配列メソッドを最初から実装する方法
序章
JavaScriptには、for
ループを超える配列を操作するためのいくつかの関数が含まれています。 自分のプロジェクトでこれらの関数を使用したことがあり、それらがどのように機能するのか、なぜ相互に使用するのか疑問に思ったことがあるかもしれません。
何かがどのように機能するかを理解するための優れた方法は、独自のバージョンを最初から作成することです。 この記事では、map
、filter
、sort
、およびreduce
の独自のバージョンを最初から作成することによってこれを行います。 完了すると、これらの関数とその使用法について理解を深めることができます。
ES6の矢印関数とJavaScriptの配列関数を組み合わせることで、非常に強力でクリーンなコードを記述できます。
JavaScript配列メソッドのしくみ
例から始めましょう。 数値の配列を反復処理し、各要素を1ずつインクリメントして、新しい配列を返すとします。 以前は、これを実現するためにいくつかのことを行う必要がありました。
- 新しい空のアレイを初期化します。
- 元の配列の各要素を繰り返し処理します。
- その要素を変更し、変更した値を新しい配列に配置します。
コードは次のようになります。
const arr = [1, 2, 3]; const newArray = []; for (let i = 0; i < arr.length; i++) { newArray[i] = arr[i] + 1; } return newArray;
ただし、組み込みのmap
関数を使用すると、次の1行のコードでこれを実行できます。
return arr.map(element => ++element);
JavaScript配列メソッドは、ES6Arrow関数を多用します。
ここで取り上げる各配列関数は、関数をパラメーターとして受け入れます。 配列の各要素を反復処理し、その関数を呼び出して、各要素をどう処理するかを決定します。 各要素を反復処理してコールバック関数を呼び出した後、新しい配列またはアイテムが返されます。
前提条件
このチュートリアルをローカルで実行するには、エディター( Visual Studio Code など)とサンドボックス環境拡張機能( Quokka.js など)が必要です。
オンラインでチュートリアルをフォローするには、CodePenまたはCodeSandboxを使用できます。
ステップ1—マップの実装
map
は、各要素を反復処理し、何らかの方法で変換し、新しい配列に追加して、新しい配列を返します。
警告:この記事では、カスタム関数を使用してJavaScriptグローバルオブジェクトを拡張します。 この方法は本番コードに副作用をもたらす可能性があるため、これは教育目的のみです。
JavaScript配列関数は、たとえばJavaのクラスの関数と同様に、配列プロトタイプの一部です。 それらを上書きするには、Array.prototype
に新しい機能を割り当てることができます。
新しい関数を作成して、Array.prototype.mapFromScratch
に割り当てましょう。
const myCustomMapFunction = function(callback) { console.log('My Custom Map Function!'); } Array.prototype.mapFromScratch = myCustomMapFunction; const arr = [1, 2, 3]; arr.mapFromScratch();
このコードを実行すると、コンソールにログメッセージが表示されます。
OutputMy Custom Map Function!
次に、for
ループを追加して、各要素を出力します。 配列自体がメソッドを呼び出すため、this
を参照することでその配列にアクセスできます。
const myCustomMapFunction = function(callback) { console.log('My Custom Map Function!'); // 'this' refers to the array for (let i = 0; i < this.length; i++) { console.log(this[i]); } }
次に、コールバック関数を呼び出して、必要な変換を実行します。 その場合、現在の要素と現在のインデックスを渡します。
const myCustomMapFunction = function(callback) { console.log('My Custom Map Function!'); // 'this' refers to the array for (let i = 0; i < this.length; i++) { const transformedElement = callback([this[i], i); } }
最後に、変換された要素を新しい配列に追加し、その配列を返します。
const myCustomMapFunction = function(callback) { console.log('My Custom Map Function!'); const newArray = []; // 'this' refers to the array for (let i = 0; i < this.length; i++) { newArray[i] = callback(this[i], i); } return newArray; }
配列内の各値をインクリメントする関数でテストして、書き換えられた関数の動作を見てみましょう。
// arr = [1, 2, 3] // expected = [2, 3, 4] console.log(arr.mapFromScratch((element) => ++element));
次の出力が表示されます。
OutputMy Custom Map Function! [2, 3, 4]
このステップでは、カスタムmap
関数を実装しました。 次に、filter
関数の実装について見ていきましょう。
ステップ2—フィルターの実装
filter
関数は、元の配列からフィルター処理された要素の新しい配列を返します。
新しい関数を作成して、Array.prototype.filterFromScratch
に割り当てましょう。
const myCustomFilterFunction = function(callback) { console.log('My Custom Filter Function!'); } Array.prototype.filterFromScratch = myCustomFilterFunction; const arr = [1, 2, 3]; arr.filterFromScratch();
次に、for
ループを設定して、各要素を反復処理します。
const myCustomFilterFunction = function(callback) { console.log('My Custom Filter Function!'); const newArray = []; for (let i = 0; i < this.length; i++) { console.log(this[i]); } }
for
ループ内で、各要素を新しい配列に追加するかどうかを決定する必要があります。 これがコールバック関数の目的であるため、条件付きで各要素を追加するために使用します。 戻り値がtrue
の場合、要素を戻り配列にプッシュします。
const myCustomFilterFunction = function(callback) { console.log('My Custom Filter Function!'); const newArray = []; for (let i = 0; i < this.length; i++) { if (callback(this[i])) { newArray.push(this[i]); } } return newArray; }
1
より大きい値を表示する関数でテストして、書き換えられた関数の動作を見てみましょう。
// arr = [1, 2, 3] // expected = [2, 3] console.log(arr.filterFromScratch((element) => element > 1));
次の出力が表示されます。
OutputMy Custom Filter Function! [2, 3]
これで、カスタムfilter
関数を実装しました。 次に、sort
関数を使用します。
ステップ3—ソートの実装
sort
関数は、元の配列からソートされた配列を返します。
新しい関数を作成して、Array.prototype.sortFromScratch
に割り当てましょう。
const myCustomSortFunction = function(callback) { console.log('My Custom Sort Function!'); } Array.prototype.sortFromScratch = myCustomSortFunction; const arr = [3, 2, 1]; arr.sortFromScratch();
このソートの実装には、バブルソートを使用します。 これが私たちが取るアプローチです:
- 配列内のアイテムを繰り返し繰り返します。
- 隣接するアイテムを比較し、順序が正しくない場合は交換します。
- 各比較を行うのに十分な回数配列を反復処理した後、配列がソートされます。
バブルソートでは、配列内の要素ごとに1回配列を完全に反復処理する必要があります。 これにより、ネストされたfor
ループが必要になります。このループでは、内側のループが最後の要素の1つ手前で停止することを繰り返します。ここで、それを追加しましょう。
注:これは教育目的であり、効率的な並べ替え方法ではありません。
const myCustomSortFunction = function(callback) { console.log('My Custom Sort Function!'); const newArray = []; for (let i = 0; i < newArray.length; i++) { for (let j = 0; j < newArray.length - 1; j++) { } } }
また、元の配列を変更する必要はありません。 これを回避するには、 SpreadOperatorを使用して元の配列を新しい配列にコピーできます。
const myCustomSortFunction = function(callback) { console.log('My Custom Sort Function!'); const newArray = [...this]; for (let i = 0; i < newArray.length; i++) { for (let j = 0; j < newArray.length - 1; j++) { } } }
コールバック関数は、現在の要素と次の要素の2つのパラメーターを受け取り、それらが正しいかどうかを返します。 この場合、コールバック関数が0
より大きい数値を返す場合、2つの要素を交換する必要があります。
const myCustomSortFunction = function(callback) { console.log('My Custom Sort Function!'); const newArray = [...this]; for (let i = 0; i < newArray.length; i++) { for (let j = 0; j < newArray.length - 1; j++) { if (callback(newArray[j], newArray[j + 1]) > 0) { // swap the elements } } } }
要素を交換するには、1つのコピーを作成し、最初の要素を置き換えてから、2番目の要素をコピーに置き換えます。 終了すると、新しくソートされた配列が返されます。
const myCustomSortFunction = function(callback) { console.log('My Custom Sort Function!'); const newArray = [...this]; for (let i = 0; i < newArray.length; i++){ for (let j = 0; j < newArray.length - 1; j++) { if (callback(newArray[j], newArray[j + 1]) > 0) { const temp = newArray[j + 1]; newArray[j + 1] = newArray[j]; newArray[j] = temp; } } } // array is sorted return newArray; }
低から高にクイックソートする関数でテストして、書き直した関数の動作を見てみましょう。
// arr = [3, 2, 1] // expected = [1, 2, 3] console.log(arr.sortFromScratch((current, next) => current - next));
次の出力が表示されます。
OutputMy Custom Sort Function! [1, 2, 3]
カスタムsort
関数を作成したので、reduce
関数の実装に進む準備ができました。
ステップ4—Reduceの実装
reduce
関数は、各要素を繰り返し処理し、1つの単一の値を返します。
reduce
は、他の関数のように新しい配列を返しません。 実際には、配列内の要素を1つの最終値(数値、文字列、またはオブジェクト)に「削減」します。 reduce
を使用する最も一般的な理由の1つは、数値の配列内のすべての要素を合計する場合です。
新しい関数を作成して、Array.prototype.reduceFromScratch
に割り当てましょう。
const myCustomReduceFunction = function(callback) { console.log('My Custom Reduce Function!'); } Array.prototype.reduceFromScratch = myCustomReduceFunction; const arr = [1, 2, 3]; arr.reduceFromScratch();
reduce
が1つの最終値を返すには、操作する開始値が必要です。 ユーザーが渡すコールバック関数は、配列の各要素に基づいてこのアキュムレータを更新し、最後に返す方法を決定します。 コールバック関数は、更新されたアキュムレータを返す必要があります。
ここでfor
ループを追加し、コールバック関数を呼び出します。 戻り値が新しいアキュムレータになります。 ループが終了したら、アキュムレータを返します。
const myCustomReduceFunction = function(callback, accumulator) { console.log('My Custom Reduce Function!'); for (let i = 0; i < this.length; i++) { accumulator = callback(accumulator, this[i]); } return accumulator; }
配列の内容を合計する関数でテストして、書き直した関数の動作を見てみましょう。
// arr = [1, 2, 3] // expected = 6 console.log(arr.reduceFromScratch((accumulator, element) => accumulator + element, 0));
OutputMy Custom Reduce Function! 6
結論
JavaScriptの配列関数は非常に便利です。 このチュートリアルでは、配列関数を再実装して、それらがどのように機能するかをよりよく理解し、それらをより効果的に使用できるようにしました。