繰り返し・反復処理配列・連想配列

【JavaScript】 forEach/map/filter/reduceを根本的に理解する

更新日:2021/03/23

JavaScriptには配列を処理するのに便利なforEach()などのメソッドがあります。

しかし定型的に使っていて、あまりよく理解していないのではないでしょうか。

そこで今回は、forEach/map/filter/reduceの解説に加え、裏側で何をやっているのかを考えてみます。

 

Array.prototype.forEach()

まずはforEachからいきましょう!

forEach( )の構文

forEach( )は、配列の要素を一つずつ抜き出し、それぞれに任意の処理をおこなうことができるメソッドです。

forEach( )構文

originalArray.forEach( callBack [, thisObj ] );

callBackはコールバック関数で、配列の値に対し順番に1回ずつ呼び出されます。。

callBackの形式

関数:function callBack( value [, index [ , arry ] ]) { 処理  }

アロー関数:(  value [, index [ , arry ] ] ) => { 処理  }

引数:

value : 要素の値
index : 要素のインデックス番号(省略可)
arry : 処理中の配列(省略可)

thisObjは、コールバック関数内でthisとして使用するオブジェクトです。
詳しくはthisObjについてについてを見てください。

リターン値:

コールバックからのリターン値は不要です。

forEach( )の使い方

値を受け取って、何かする!


const a=[2,4,6];
a.forEach( function (e) {
        console.log( e );
    });

forEach( )の裏側の仕組み

ではforEach()内部で、どのような処理をしているか妄想してみます。


Array.prototype.forEach = function( callBack , thisObj = undefined ){
        if( thisObj !== undefined) callBack = callBack.bind(thisObj) ;
        const length = this.length;  
        for( let i = 0 ; i < length ; i ++ ){
                callBack( this[ i ] , i , this ); // コールバック呼び出し
        }
};

thisObjが指定されたら、コールバック関数にbind()メソッドを使ってthisをセットします。

後はデータの数だけループで回して、コールバック関数に値などを渡しているだけです。

もっといろいろやっていると思いますが、要点はこんな感じですね。

■参考:実際のforEach( )アルゴリズム

中断できるforEach( )→some()/every()

上の妄想例のように、forEach()は問答無用でループしているので、コールバック側で中断しようと思ってもできません。

中断したいときはforEach()ではなく、some()やevery()を使用します。
【JavaScript】 forEach()を中断させる一番スマートな方法

 

Array.prototype.map()

次はmapです。

map( )の構文

map( )は、配列を元に同じ要素数の配列を作成するメソッドです。

構文

let newArray = originalArray.map( callBack [, thisArray ] );

callBackは、新しい配列の要素を決定する関数です。

callBackの形式

関数:function callBack( value [, index [ , arry ] ]) { 処理  }

アロー関数:(  value [, index [ , arry ] ] ) => { 処理  }

引数:

value : 要素の値
index : 要素のインデックス番号(省略可)
arry : 処理中の配列(省略可)

thisObjは、コールバック関数内でthisとして使用するオブジェクトです。
詳しくはthisObjについてについてを見てください。

リターン値:

コールバックからのリターン値は、新しい要素の値です。

map( )の使用例

よくある使用例。

配列の中身を2倍する


const a = [ 1 , 2 , 3 ].map(
    function( e ) { return e * 2; }
);
console.log( a ); // Array(3) [ 2 , 4 , 6]

配列の次元を増やすこともできます。

2次元に変換する

const a = [ 1 , 2 , 3 ].map(
    function( e , index ) { return [ index , e ] }
);
console.log( a ); // Array(3) [ [ 0 , 1 ] , [ 1 , 2 ] , [ 2 , 3 ] ]

そもそも要素のデータ型を揃える必要はありません。

何を返してもいい


const a = [ 1 , 2 , 3 ].map(
    function( e , index ) {
        switch(index){
            case 0: return "test!";
            case 1: return { x:10 , y:20 };
            default: return undefined;
        }
    };
);
console.log( a ); // Array(3) [ "test!" , { x:10 , y:20 } , undefined }

map( )の裏側の仕組み

ではmap()内部で、どのような処理をしているか妄想してみます。


Array.prototype.map = function( callBack , thisObj = undefined ){
        if( thisObj !== undefined) callBack = callBack.bind(thisObj) ;
        const length = this.length;  
        const array = []; // ワーク用配列
        for( let i = 0 ; i < length ; i ++ ){
                array.push( callBack( this[ i ] , i , this ) ) // コールバック呼び出し&ワーク用配列にセット
        }
        return array;
};

基本はforEach()と同じですが、コールバックからリターン値を受け取り、ワーク用配列にpush()。
最後にワーク用配列をリターンしている点が異なります。

この例からわかるようにmap()メソッド内では、元の配列の内容を変更していません。

■参考:実際のmap( )アルゴリズム

 

Array.prototype.filter()

次はfilterです。

filter( )の構文

filter( )は、任意の要素だけで新しい配列を作成するメソッドです。

構文

let newArray = originalArray.filter( callBack [, thisArray ] );

callBackは、新しい配列に元の要素を残すかどうかを決定する関数です。

callBackの形式

関数:function callBack( value [, index [ , arry ] ]) { 処理  }

アロー関数:(  value [, index [ , arry ] ] ) => { 処理  }

引数:

value : 要素の値
index : 要素のインデックス番号(省略可)
arry : 処理中の配列(省略可)

thisObjは、コールバック関数内でthisとして使用するオブジェクトです。
詳しくはthisObjについてについてを見てください。

リターン値:

元の要素を新しい配列に残す場合はtrue。
削除する場合はfalseをリターンします。

filter( )の使用例

よくある例。

偶数のみの配列を新規作成する


const a = [ 1 , 2 , 3 , 4 , "a" ].filter(
    function (e) {
        return e % 2 ===0;
    }
);
console.log( a );  // [ 2 , 4 ]

filter( )の裏側の仕組み

ではfilter()内部で、どのような処理をしているか妄想してみます。


Array.prototype.filter = function( callBack , thisObj = undefined ){
    if( thisObj !== undefined) callBack = callBack.bind(thisObj) ;
        const length = this.length;
        const array = []; // ワーク用配列
        for( let i = 0 ; i < length ; i ++ ){
            if(  callBack( this[ i ] , i , this ) ) array.push( this[ i ] );
        }
        return array;
};

コールバックからのリターン値が偽と判断される値のとき、ワーク用配列に対象となる値をセットしています。

こちらも他のメソッドと同様に、元の配列は変更されません。

■参考:実際のfilter( )アルゴリズム

 

Array.prototype.reduce()

次はreduceです。
reduceは、これまで紹介してきたメソッドとは少し違います。

reduce( )の構文

構文

let newValue = originalArray.reduce( callBack [, initialValue ] );

callBackは、要素の値などを受け取り、計算結果を返す関数です。

callBackの形式

関数:function callBack( nowValue , value [ , index [ , arry ] ]) { 処理  }

アロー関数:( nowValue , value [ , index [ , arry ] ] ) => { 処理  }

引数:

nowValue : 現在の値
value : 要素の値
index : 要素のインデックス番号(省略可)
arry : 処理中の配列(省略可)

initialValue : nowValueの初期値

initialValueを指定した場合

    1回目のコールバック呼び出しの引数は、次のようになります。

    nowValue = initialValue
    value = 一番目の要素

initialValueを指定しない場合

    1回目のコールバック呼び出しの引数は、次のようになります。

    nowValue = 一番目の要素
    value = 二番目の要素

リターン値:

    計算結果をリターンします。
    リターンした結果は、次のコールバックのnowValueにセットされます。

reduce( )の使用例

よくある例。

配列の和を求める


const a = [ 1 , 2 , 3 ].reduce(
    function( nowValue , value ){
        return nowValue + value;
    }
);
console.log( a ); // 6

新しいオブジェクトを作成する。(*補足)

オブジェクトを作成


const a = [ 1 , 2 , 3 ,4 , "a" ].reduce(
    function ( n , e , index) {
        n["data" + index] = e;
        return n;
    },{}
);
console.log( a ); // Object { data0: 1, data1: 2, data2: 3, data3: 4, data4: "a" }

reduce( )の裏側の仕組み

ではreduce()内部で、どのような処理をしているか妄想してみます。

Array.prototype.reduce = function( callBack , initialValue  = undefined ){
    const length = this.length;
    let [ nowValue , i ] = initialValue ?  [ initialValue , 0 ] : [ this[0] , 1 ] ;
    for(  ; i < length ; i ++ ){
        nowValue = callBack( nowValue , this[i] , i , this );
    }
    return nowValue;
};

空配列や要素数1のときの処理がありませんが、雰囲気としてはこんな感じです。

ようするに、「必要な情報渡すから、勝手に計算してね☆」というメソッドでした。

補足

callBack関数の一番目と二番目の引数は、同じ形式でなくても問題ありません。


const result = [ 1 , 2 , 3 ].reduce(
    function( n , e , index , arry ){
          n.sum += e;
          n.siki +=  (index !== 0 ? "+" : "") + e
                          + ( index < arry.length -1 ? "" : "="+n.sum);
          return n;
    },{ siki : "" , sum : 0 } );

上の例では一番目の引数はオブジェクト型、2番目の引数は数値です。

重要なのは、一番目の引数と同じ形式で返すことです。

 

forEach()/map()/filter()のthisObjについて

forEach()/map()/filter()は、引数を通してコールバックの引数を指定することができます。

例えば次のようなコードがあるとします。


[ 1 , 2 , 3 ].forEach(
    function( e ){
        console.log( this ); // window
    }
);

この場合コールバック関数内のthisは、非strictモードならwindow、strictモードならundefinedです。

しかし2番目の引数を指定すると…

[


 1 , 2 , 3 ].forEach(
    function( e ){
        console.log( this ); // Object { x : 10 }
    } , { x : 10 }
);

コールバック関数内のthisが、指定したものになりました。

つまり2番目の引数にオブジェクトのthisを渡すことで、コールバック関数内でも使用できます。


function(){
        this.x = 10;
        [ 1 , 2 , 3 ].forEach(
                function( e ){
                        console.log( this.x + e );
                } , this
        );
}

ただし上のような使い方をするなら、アロー関数の方がいいかもしれません。


function(){
        this.x = 10;
        [ 1 , 2 , 3 ].forEach(
                 e =>{
                        console.log( this.x + e );
                }
        );
}

 

まとめ

今回はforEachなどのメソッドが、内部でどんな処理をやっているのかを考えてみました。

今まで定型文的に使っていたメソッドが、内容を理解したことでもっと多くのパターンで使用できるようなりましょう。

更新日:2021/03/23

書いた人(管理人):けーちゃん

スポンサーリンク

記事の内容について

null

こんにちはけーちゃんです。
説明するのって難しいですね。

「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。

裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。
そんなときは、ご意見もらえたら嬉しいです。

掲載コードについては事前に動作確認をしていますが、貼り付け後に体裁を整えるなどをした結果動作しないものになっていることがあります。
生暖かい視線でスルーするか、ご指摘ください。

ご意見、ご指摘はこちら。
https://note.affi-sapo-sv.com/info.php

 

このサイトは、リンクフリーです。大歓迎です。