オブジェクト繰り返し・反復処理配列・連想配列

【JavaScript】 オブジェクト(連想配列)でforEachする方法

更新日:2021/07/29

PHPなどでオブジェクトを列挙するためにforeach文を使用する。

だからJavaScriptにもforEachがあったら使っちゃうよね。
でも使えないのよ。

どうして?
どうすればいいの?

というお話

 

なぜオブジェクトでforEachできないのか

まずは『どうしてオブジェクトでforEachが使用できないのか』についての疑問を解消してみます。

forEachはメソッドだから

PHPのforeachは、プログラム構文。
if文などと同じ位置づけです。

一方JavaScriptのforEachはメソッド。
メソッドとはオブジェクトが持っている、実行可能なプロパティです。


const a = {
    x : 10 ,   // プロパティ変数
    func : function(){ };  // プロパティメソッド
    forEach :  function(){ }; // ← forEachというメソッドがあれば実行できる
}

オブジェクトでforEach()が使用できないのは、forEach()メソッドを持っていないので実行できないというだけなんです。

もちろんオブジェクトに自分で流れを考えてforEachを組み込めば、forEachを実行できます。

forEachメソッドを持つオブジェクト

JavaScriptの組み込み(標準)オブジェクトで、forEachメソッドを持つものを挙げてみます。

Arrayオブジェクト

Arrayオブジェクトはいわゆる配列と呼ばれているものです。
次のように、配列が持つ値を順番に列挙できます。


[ 1 , 2 , 3 ].forEach( 
       e => console.log(e);  // 1回目:1    2回目:2    3回目:3
);

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

Mapオブジェクト

Mapオブジェクトは、キーと値、そしてセットした順番を保持するオブジェクトです。
MapオブジェクトのfoeEach()メソッドは、キーと値を列挙することができます。


const map = new Map();
map.set("key1",1);
map.set("key2",2);

map.forEach(
    function( value ,key  ){
        console.log( key , value );
    }
);

 

オブジェクトをforEach()以外で列挙する

オブジェクトの列挙にforEachは使えません。
そこで他の方法を使ってみます。

for each...in【廃止】

古いコードで使われていることがあるけれど、今は使用不可
ネット上でもたまに見るので、混乱しないように挙げておく。

for each...in構文【廃止のため使用不可】


var obj = { key1: 1,key2: 2, key3: 3 };

for each ( var val in obj ) {
  console.log( val );  //   // 1回目:1    2回目:2    3回目:3
}

for...in【非推奨】

for...inは現在でも使用できますが、非推奨となっている構文です。
他の構文を使用するべきです。

for...inはプロトタイプチェーンをたどります。
参考:【JavaScript】 プロトタイプとは?prototypeプロパティはプロトタイプではない件について


Object.prototype.p1 = 40; // Objectオブジェクトのprototypeにプロパティをセット
                                      // ほんとは組み込みオブジェクトを操作してはダメ

const a = function () {      // aという名前のコンストラクタを作成
                                      // 同時にObjectオブジェクトのprototypeが
                                      // aのプロトタイプチェーンに組み込まれる
    this.x =10;
    this.y = 20;
};
a.prototype.p2 = 30;         // aにprototypeプロパティ追加

const data = new a;        // コンストラクタからオブジェクト作成

for( const key in data ){
    console.log( key , data[key] );   //  1回目: x 10
                                               //  2回目: y 20
                                               //  3回目: p2 30
                                               //  4回目: p1 40
}

プロトタイプチェーンまでたどってしまうので、オブジェクトの内容のみ列挙したいとき困ります。

そんなときはhasOwnProperty()で、プロトタイプチェーン内のプロトタイプではなくて自分自身のプロパティかどうかチェックします。


for( const key in data ){
  if ( data.hasOwnProperty( key ) ) 
        console.log( key , data[key] );   //  1回目: x 10
                               //  2回目: y 20
}

Object.keys()を使う

オブジェクト内容の列挙は、今のところObject.keys()を使うことが推奨されています。

Object.keys()は、オブジェクトのキーが格納された新規配列を作成するメソッドです。
このとき、プロトタイプチェーンはたどりません。

そして新規作成された配列に対してforEach()メソッド使用します。


Object.prototype.p1 = 40;
const a = function () {this.x =10;this.y = 20;};
a.prototype.p2 = 30;

const data = new a;

Object.keys(data).forEach(
    key => console.log(key )  //   1回目: "x"
                               //   2回目: "y"
);

場合によっては、Object.entries()またはObject.values()を使用したほうが効率的です。

Object.entries():[ キー , 値 ]を要素とした配列を作成します。


Object.entries(data).forEach(
    entry => console.log(entry)  //   1回目: [ "x," 10]
                               //   2回目: [ "y" , 20]
);

Object.values(): 値 を要素とした配列を作成します。


Object.values(data).forEach(
    value => console.log(value)  //   1回目:  10
                               //   2回目:  20
);

 

どうしてもforEach()したいとき

やっぱりforEach()が大好きだから、使いたいのよ!!

という人は、オブジェクトにforEach()メソッドを自作しましょう。

既存の方法でforEach()

Object.keys()を使って列挙します。


const a = function () {
    this.x =10;
    this.y = 20;
};
a.prototype.forEach = function ( callBack ) { // Object.keys()で列挙しないようにするため
                                         // prototypeにforEachメソッド追加
    Object.keys(this).forEach(
        key => callBack( key , this[key] )
    );
};

const data = new a;

data.forEach(
    ( key , value ) => console.log( key , value ) // 1回目:  x 10
                                         // 2回目:  y 20
);

任意の値をforEach()

好きな値を好きな順番でforEach()することも可能です。

const a = function () {
    this.x = [ 10 , 20 , 30 ];
    this.y = 20;
    this.z = [ "a" , "b" , "c" ];
};
a.prototype.forEach = function ( callBack ) {
    for( let i = 0 ; i < this.x.length ; i ++) callBack( this.x[ i ] );
    callBack( this.y );
    for( let i = 0 ; i < this.y.length ; i ++) callBack( this.y[ i ] );
};

const data = new a;

data.forEach(
    (  value ) => console.log( value )
);

禁断のObject.prototype.forEach

Object.prototypeにforEachを自作すれば、全てのオブジェクトでforEachできるようになります。


Object.prototype.forEach = function ( callBack ) {
    Object.keys(this).forEach(
        key => callBack( key , this[key] )
    );
};

const a = { x : 10 , y : 20 };

a.forEach(
    (  key , value ) => console.log( key , value )
);

[ 1 , 2 , 3 ].forEach( // 既存のforEachに影響はなし
    e => console.log( e )
);

ただし、第三者作成したライブラリなどでObject.prototype.forEachを定義している場合、読み込む順番によって、そちらのコードが優先される可能性があります。
悪意あるコードが仕組まれることもあるので、使用は自己責任で!

というか使ってはいけません。
※こういうこともできるという意味で、挙げてみました。

 

ジェネレーターを使用してfor...of列挙をおこなう

・オブジェクト内の任意の値(キー)を列挙したい。
・コールバックは嫌だ

という人は、ジェネレーターとfor...of構文を使用しましょう。


const a = { x : 10 , y : 20 };

a[Symbol.iterator] = function* (){
        yield this.x;
        yield this.y;
};

for( let val of a){
        console.log( val );
}

なおジェネレーター関数は、コールバック関数内でyieldできません。
次のコードはエラーになります。


a[Symbol.iterator] = function* (){
    Object.keys(this).forEach(
        e => yield this[e]
    );
};

次のように、forループを使えばOKです。


a[Symbol.iterator] = function* (){
    const keys = Object.keys( this );
    for( let i = 0 ; i < keys.length ; i ++ )
        yield this[ keys[ i ] ];
};

ちなみにインスタンス化するなら、prototypeプロパティにイテレーターを定義します。


const a = { x : 10 , y : 20 };

a.prototype[Symbol.iterator] = function* (){
        yield this.x;
        yield this.y;
};

const obj = new a;

for( let val of obj){
        console.log( val );
}

 

まとめ

forEachは構文ではなくてメソッドなのよ!
だから自作のオブジェクトでは使えないのよ!

というお話でした。

forEachを定型の構文みたいに受け取れる記事があるので、初心者だと難しいよね。

僕だけか…?

更新日:2021/07/29

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

スポンサーリンク

記事の内容について

null

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

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

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

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

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

 

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