MENU

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

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

更新日:2021/03/23

 

 

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

 

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

 

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

 

■お願い
去年ECMAScript2020を頑張って日本語訳しましたが、誰も見てくれません・・・
誰かみて!!
【JavaScript】 学習のためECMAScript2020を日本語訳してみました

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などのメソッドが、内部でどんな処理をやっているのかを考えてみました。

 

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

記事の内容について

 

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


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

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

そんなときは、ご意見もらえたら嬉しいです。

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

【お願い】

お願い

■このページのURL


■このページのタイトル


■リンクタグ