【JavaScript】 アレイライク・配列風・配列のようなオブジェクトとは
更新日:2023/06/22
JavaScriptには言語仕様上『array-like object』というものがあります。
アレイライクオブジェクト、または配列風オブジェクト、または配列のようなオブジェクトと日本語訳されています。
つまり、配列に似ているけれど配列ではないオブジェクトですね。
2023/6/23 内容を整理しました
アレイライクなオブジェクトとは
アレイライクなオブジェクトとは、一言でいうと値が整数のlengthプロパティを持っているオブジェクトです。
ECMAScriptでは次のような処理でエラーにならないとき、アレイライクなオブジェクトと定義されています。
Assert: Type(obj)はObject型
? ToLength(? Get( obj, "length") ) の結果を返す
■https://ecmascript2020言語仕様.com/abstract-operations/#h-lengthofarraylikeより
1行目は対象がオブジェクトかどうかを確認しています。
2行目はオブジェクトからlengthを取得して、オブジェクトの長さとして使用するのに適した整数に変換しています。
ただ2行目は、オブジェクトにlengthプロパティがないとき結果が0になります。
また、lengthプロパティが数字と判断できる文字列ならその数値、判断できないなら0になります。
つまり、エラーになりません。
例外がスローされるのは、プロパティ値がSymbol型とBigInt型の時です。
それ以外はエラーにならないので、ほとんどのオブジェクトはアレイライクと言えます。
しかし、それでは運用として使いにくいです。
lengthプロパティを持つオブジェクトをアレイライクなオブジェクトと呼ぶのが無難なので、冒頭のような表現をしています。
また、ECMAScriptには次のような文もあります。
通常、配列のようなオブジェクトには、整数のインデックス名を持ついくつかのプロパティもあります。 ただし、これはこの定義の要件ではありません。
配列は整数インデックスを持っている、という認識が一般的です。
そのため、アレイライクにも整数インデックスが必要な気がします。
しかし整数プロパティの有無は、アレイライクかどうかの判定に含まれないということですね。
Arrayオブジェクトとアレイライクなオブジェクトの違い
Arrayオブジェクトはlengthプロパティを持っています。
そのため、Arrayオブジェクトはアレイライクなオブジェクトです。
しかしプログラムコード上ではArrayオブジェクトと、その他のアレイライクオブジェクトは別のものとして扱われます。
■そもそもArrayオブジェクトとは?
Arrayオブジェクトは、エキゾチックなオブジェクトの一つで、配列としての機能を持ったオブジェクトです。
配列を処理するメソッドを持っているのはもちろんですが、それとは別に、要素の追加や削除などを行うとlengthプロパティを調整する機能が備わっています。
その反対に、lengthプロパティを変更すると、既存要素の数が増減するという機能もあります。
ブラウザ上で動作させることを目的としたJavaScriptコードでよく話題になるのが、DOMのgetElementsByClassNameメソッドです。
このメソッドの使用目的は、対象となる複数のDOM要素を取得して、全ての要素に個別に処理をおこないたいというものが考えられます。
そこで候補に挙がるのがforEachメソッドです。
やってしまいがちな間違いコード
const elements = document.getElementsByClassName( className );
elements.forEach( e=>console.log( e ) );
// Uncaught TypeError: elements.forEach is not a function
しかし、エラーが出てしまいました。
どうしてでしょうか?
実はgetElementsByClassNameメソッドは、Arrayオブジェクトでななくて、HTMLCollectionオブジェクトを返しています。
HTMLCollectionオブジェクトはlengthプロパティを持っているので、アレイライクなオブジェクトです。
しかしこのオブジェクトにはforEachメソッドが定義されていないので、上記のコードでエラーになってしまったのです。
オブジェクトがArrayオブジェクトではないので、forEachが使用できないという表現をすることがあります。
しかしforEachが使用できないのとArrayオブジェクトは関係ありません。
オブジェクトにforEachが定義されているかいないかの違いだけです。
APIを含めて組み込みオブジェクトのメソッドには、アレイライクなオブジェクトを返すものがあります。
このとき、プログラマーがArrayオブジェクトだと勘違いしていることがよくあります。
バグの原因にもなるので、何が返ってきているのか確認が必要です。
アレイライクが使用可なArrayオブジェクトのメソッド
Arrayオブジェクトのメソッドには、アレイライクなオブジェクトを受け付けるものがあります。
いくつか挙げてみます。
forEach
Array.prototype.forEach.call を呼び出すことで、アレイライクなオブジェクトに対してforEachを実行できます。
存在しないインデックスは無視されます。
アレイライクなオブジェクトでforEach
const a = {
0:10 , 1:undefined , 3:40,
a:4,
length:4,
}
Array.prototype.forEach.call( a ,
(value,index)=>console.log(index + ":" + value)
);
// 結果:
// 0:10
// 1:undefined
// 3:40
indexOf
Array.prototype.indexOf.call を呼び出すことで、アレイライクなオブジェクトに対してindexOfを実行できます。
アレイライクなオブジェクトでindexOf
const a = {
0:10 , 1:undefined , 3:40,
a:4,
length:4,
}
const result = Array.prototype.indexOf.call( a , undefined );
console.log( result );
// 結果:
// 1
検索値をundefinedにした場合、存在しないインデックスではなくて、undefined値を持っているインデックスが返ります。
join
Array.prototype.join.call を呼び出すことで、アレイライクなオブジェクトに対してjoinを実行できます。
インデックスが存在しないまたは、値がundefinedかnulなら、空の文字列として扱われます。
アレイライクなオブジェクトでjoin
const a = {
0:10 , 1:undefined , 3:40,
a:4,
length:4,
}
const result = Array.prototype.indexOf.call( a , undefined );
console.log( result );
// 結果:
// 10|||40
map
Array.prototype.map.call を呼び出すことで、アレイライクなオブジェクトに対してmapを実行できます。
インデックスが存在しない場合、コールバック関数が呼び出されません。
その結果、map関数の戻り値である配列に、そのインデックスが作成されません。
アレイライクなオブジェクトでindexOf
const a = {
0:10 , 1:undefined , 3:40,
a:4,
length:4,
}
const result = Array.prototype.map.call(a,
(value,index)=>{
console.log( index + ":" + value );
return value;
});
// console.log結果:
// 0 :10
// 1: undefined
// 3: 40
console.log( result );
// 結果:
// Array(4) [
// 0 :10
// 1: undefined
// 3: 40
// length: 4
// ]
Array.prototypeと書くのがめんどくさい
Arrayオブジェクトのメソッドにアレイライクなオブジェクトを渡すとき、Array.prototype.メソッド名.callまたはapplyと記述します。
一回だけならいいですが、何度も呼び出すと少し冗長な気がしてきますね。
そんなときは、次のようにメソッドを変数にセットできます。
const indexOf = Array.prototype.indexOf;
indexOf.call( a , undefined );
indexOf.call( a , 10 );
indexOf.call( a , 40 );
他の組み込みオブジェクトでも応用できるので、試してみてください。
更新日:2023/06/22
関連記事
スポンサーリンク
記事の内容について
こんにちはけーちゃんです。
説明するのって難しいですね。
「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。
裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。
そんなときは、ご意見もらえたら嬉しいです。
掲載コードについては事前に動作確認をしていますが、貼り付け後に体裁を整えるなどをした結果動作しないものになっていることがあります。
生暖かい視線でスルーするか、ご指摘ください。
ご意見、ご指摘はこちら。
https://note.affi-sapo-sv.com/info.php
このサイトは、リンクフリーです。大歓迎です。