MENU

JavaScript配列・連想配列

【JavaScript】 配列と連想配列の要素順序とMapオブジェクト

更新日:2020/06/19

 

JavaScriptでプログラムを作成していると、配列や連想配列を使うことって多いですね。
僕の場合、入れた順番で取り出したいことがあります。

 

しかし思ったようにいかず、悩むこともしばしば…
そこで、配列や連想配列内の要素の並び方を調べてみました。

 

また、Mapオブジェクトを使った連想配列の代替案をお伝えします。

 

配列の挿入順序は保証される

 

配列は要素を挿入した順番を記憶しているので、その順番で取り出すことができます。

 

挿入した順番に取り出し可能

 

次のコードは、いろいろな方法で配列に要素を挿入して、内容を出力しています。

 

JavaScript

 


let array=["a","c",1,"v","1"];
array.push("d");
array.push("2");
array.push(2);
array[10]="z";
array[9]="x";

console.log(array);

 

実行結果

 


Array(11) [ "a", "c", 1, "v", "1", "d", "2", 2 , undefined, "x", "z"]

 

要素の形式に関係なく、順番に格納されているのがわかります。

 

ただしインデックス付きでセットした場合は、指定した位置にセットされています。
インデックス10の後に9をセットしていますが、それぞれの位置にセットされていますね。

 

ちなみにインデックス8は、何もセットされていないのでundefined(※)と表示されています。

 

※ブラウザによって表示される内容が異なります。
最近再確認したところ、Firefox77.0.1では<1 empty slot>と表示されていました。
Google Chrome83.0.4103.116では、emptyと表示されていました。

 

 

forEachで順番に取り出し

 

セットした要素は、forEachで順番に取り出すことができます。

 

JavaScript

 


console.log(array);

array.forEach( ( v , i )=>{
    console.log( `index:${ i } value: ${ typeof v === "string" ? `"${v}"` : v } `);
});

※forEachメソッドの2番目の引数は、配列のインデックス番号です。

 

 

 

 

ただし8番目は要素がないので、取り出せません。

 

実行結果

 


Array(11) [ "a", "c", 1, "v", "1", "d", "2", 2 , undefined, "x", "z"]

index:0 value: "a"
index:1 value: "c"
index:2 value: 1 
index:3 value: "v"
index:4 value: "1"
index:5 value: "d"
index:6 value: "2"
index:7 value: 2
index:9 value: "x"   ← index:8がないのでスキップされた
index:10 value: "z" 

 

なお、明示的にundefinedをセットすると、forEachで取り出すことができます。

 

JavaScript

 


array[8]=undefined;  ← index:8に値をセット

array.forEach( ( v , i )=&gt;{
    console.log( `index:${ i } value: ${ typeof v === "string" ? `"${v}"` : v } `);
});

 

実行結果

 


Array(11) [ "a", "c", 1, "v", "1", "d", "2", 2 , undefined, "x", "z"]

index:0 value: "a"
index:1 value: "c"
index:2 value: 1 
index:3 value: "v"
index:4 value: "1"
index:5 value: "d"
index:6 value: "2"
index:7 value: 2
index:8 value: undefined   ← index:8が表示された
index:9 value: "x" 
index:10 value: "z" 

 

 

forループで取り出し

 

インデックスを参照したいときは、前項のようにforEachのコールバック関数の二つ目を利用するか、forループを使います。
インデックスの最大値は、lengthプロパティ-1で取得できます。

 


let len = array.length;
console.log("インデックス最大値=" + (len-1));
for(let i = 0; i < len ; i++){
    console.log(i.toString() + ':' + array[i]); 
}

 

実行結果

 

インデックス最大値=10
0:a
1:c
2:1
3:v
4:1
5:d
6:2
7:2
8:undefined
9:x
10:z

 

配列もオブジェクト

 

そもそもJavaScriptの配列は、基本的なオブジェクトから派生させたArrayオブジェクトです。

 

初期化等で特殊な表記ができる点が特殊ですが、基本的には言語として仕様が決まっているだけで、僕たちが独自に作成したオブジェクトと基本的には同じ位置付けです。

 

機能としては、Arrayオブジェクトに実装されているメソッド内で、挿入した順番にインデックスを付与したり、インデックス順に出力したりしています。

 

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

連想配列の挿入順序は保証されない

 

連想配列は、挿入した順番を記憶していません。
そのため、順番に取り出すことができません。

 

そもそも、JavaScriptには連想配列という仕組みはありません。

 

連想配列ではなくオブジェクト

 

連想配列とは、数値以外のキーも使用できる配列です。
ネットで連想配列を作成する方法を調べると、次のような方法を紹介していることがあります。

 

JavaScript

 


let rensoHairetsu={"a":"1","b":"2","b":"3"};

 

上のコードは、実際には連想配列ではなくてオブジェクトを作成しています。
またキーではなく、プロパティと呼びます。

 

JavaScriptで連想配列という言葉を使うと、笑われてしまうので注意しましょう。

 

 

数値プロパティが先にくる

 

では次のようなオブジェクトを作成して、内容を確認してみます。

 

JavaScript

 


let obj={  "a" : "1"  ,  "z" : "2"  ,  "b" : "3"  ,  1 : "4"  ,  "1" : "5"  };
obj.c="6";
obj.e="7";
obj[2]="8";

console.log(obj);

 

実行結果

 

Object {  1: "5"  ,  2: "8"  ,  a: "1", z: "2"  ,  b: "3"  ,  c: "6"  ,  e: "7" }

 

 

実行結果を見ると、挿入した順番になっていません。

 

アルファベットのプロパティは、挿入順になっています。
しかし数値のプロパティが先頭にきています。

 

※これらの挙動はブラウザによって異なります。

 

また上の例では、次のように同じ数値のプロパティを、数字と文字列で指定しています。

 

1 :  "4" ← プロパティを数値で指定
"1" : "5" ← プロパティを文字列で指定

 

しかし実行結果には、次の値しか残っていません。

 

1: "5"

 

数字・文字列にかかわらず同じ数値は、同じプロパティと判断されて、最後にセットした値が残るのです。

 

 

 

forEachでの取り出し

 

次に、プログラムで順番に取り出してみます。
自分で作成したオブジェクトはforEachメソッドを持っていません。
プロパティを列挙したい場合はObject.keysでプロパティの配列を取得して、その配列に対してforEachを使用します。

 

JavaScript

 


Object.keys(obj).forEach(function(key) {
  console.log(key+":"+obj[key]);
});

// わかりやすいように分割してみる
// let key = Object.keys(obj); プロパティ一覧を配列で取得する
// key.forEach(・・・               取得したプロパティ一覧の配列をforEachで列挙
//

 

実行結果

 


1 : 5
2 : 8
a : 1
z : 2
b : 3
c : 6
e : 7

 

一応ですが、最初の実行結果で得られた順番になっています。

 

ですがObject.keys()の解説に次のように書いてあります。

 

Object.keys() メソッドは、指定されたオブジェクトが持つプロパティの 名前の配列を、通常のループで取得するのと同じ順序で返します。
引用:https://developer.mozilla.org/

 

通常のループとはなんでしょうか…
なんともあやふやな表現ですね。

 

オブジェクトからプロパティを取り出すときの順番は、明確な法則がありません。

 

オブジェクトに値をセットした順番に依存したプログラムは、作成するべきではありません。

 

配列で連想配列を実現する※非推奨

 

2次元の配列を利用するすることで、連想配列のようなものを実現できます。

 

連想配列のようなもの : キーと値からなるデータ保持機能

 

JavaScript

 


Array.prototype.aset = function(key,value){
    let len = this.length;

    for(let i = 0; i < len ; i++ ){
        if( key === this[i][0]){
                        this[i][1]=value;return this;
                } 
    }
    this.push([key,value]);
        return this;
};

let array = [ ["z","0"] , ["zz","00"] ].aset("a","1").aset("b","2");

array.aset(1,"3");
array.aset("1","4");
array.aset("b","5");
array.aset(1,"6");
array.aset("1","7");

console.log(array);

 

入れ物が配列なので、順番は保持されます。
数値型と文字列型も保持されています。

 

実行結果

 


0: Array [ "z", "0" ]
1: Array [ "zz", "00" ]
2: Array [ "a", "1" ]
​3: Array [ "b", "5" ]
​4: Array [ 1, "6" ]
​5: Array [ "1&quot;, "7" ]

 

キーから値を取得するメソッドを作成します。

 

JavaScript

 


Array.prototype.avalue = function(key){
    let len = this.length;

    for(let i = 0; i < len ; i++ ){
        if( key === this[i][0])
                return this[i][1];
    }
    return false;
};

console.log( "1 = >" + array.avalue(1) );
console.log( "\"1\" = >" +array.avalue("1") );

 

実行結果

 


1=>6
"1"=>7

 

数値型と文字列型について個別に値を取得できました。

 

しかしこのコードは、要素の削除など必要な機能がたりません。
Arrayオブジェクトのプロトタイプにメソッドを追加する行為も、少々問題があります。

 

 

 

Mapオブジェクトで連想配列を実現する

 

最近知ったのですが、連想配列のような機能を持つオブジェクトがすでに存在していました。

 

Mapオブジェクトを使用すると、キーと値の保持が簡単にできます。
Map | https://developer.mozilla.org/

 

JavaScript

 


let array2 = new Map([ ["z","0"] , ["zz","00"] ]).set("a","1").set("b","2");

array2.set(1,"3");
array2.set("1","4");
array2.set("b","5");
array2.set(1,"6");
array2.set("1","7");

for (let [key, value] of array2) { // array2.forEach((key,value)=>{})も使用可能
    console.log(key + ":" + value);
}

 

セットした順番、および数値型と文字列型について個別に値を取得できました。

 

実行結果

 


z:0
zz:00
a:1
b:5
1:6
1:7

 

個別のキーの値も取得できます。

 

JavaScript

 


console.log("1=>" + array2.get(1));
console.log("\"1\"=>" +array2.get("1"));

 

実行結果

 


1=>6
"1"=>7

 

キーが存在するかどうかの確認は、has()を使用します。

 

JavaScript

 


console.log("has"+array2.has(1));

 

実行結果

 


has:true

 

要素の削除はdelete()を使用します。
成功時はtrue、削除できなかったときはfalseを返します。

 

JavaScript

 


console.log("size="+array2.size);
console.log("delete:"+array2.delete(1));
console.log("has(1):"+array2.has(1));
console.log("has(\"1\"):"+array2.has("1"));
console.log("size="+array2.size);

 

実行結果

 


size=6
delete:true
has(1):false // 数字の1は削除された
has("1"):true // 文字列の1は残っている
length=5

 

Mapオブジェクト内の要素数を取得するときは、sizeプロパティを使用します。
ついlengthを使いたくなりますが、ダメなんですね。
でも強引に使ってみます。

 

JavaScript

 


Object.defineProperty(  // Mapオブジェクトにlengthプロパティを追加
    Map.prototype, 'length',{
                   get:function(){return this.size;}
             }
   );

let array3 = new Map();
// Mapに要素を追加

console.log("length=" + array2.length);
console.log("size=" + array2.size);

 

lengthとsizeの値が同じになるはずです。
単なるネタですので、sizeを使いましょう。

 

 

 

まとめ

 

JavaScript連想配列は、正確にはオブジェクトです。
またキーは、オブジェクトではプロパティといいます。

 

オブジェクトのプロパティは、挿入順を記憶していません。
そのため、列挙順は不定です。

 

配列やMapオブジェクトなど挿入順で列挙できるオブジェクトが存在しますが、それらは、挿入順を記憶していないオブジェクトから派生させたものです。

 

そして派生させたオブジェクトに、データを追加した順を記憶するようにコードを実装しているのです。

 

 

 

記事の内容について

 

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


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

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

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

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