はじめに
JavaScriptには、配列ではないが、配列に似たオブジェクトであるArray-like object と呼ばれるオブジェクトがあります。
配列のように扱えるが、配列の機能が全て使用できるわけではない、この配列のようなオブジェクトについてみていきたいと思います。
Array-like object、配列のようなオブジェクトとは
Array-like object とは、以下の配列の特徴をもつが、配列ではないオブジェクトのことです。
-
length
プロパティをもつ - 0から始まる数値インデックスによる要素へのアクセスが可能
Array-like object は配列ではないため、map
やfilter
といった配列メソッドを直接呼び出すことができません。
querySelectorAll()
Array-like object を返す関数として、querySelectorAll()
が挙げられます。
querySelectorAll()
は引数に渡したセレクタに合致したNodeList
を返しますが、このNodeList
は配列ではなくArray-like object です1。
NodeList
からは、map
やfilter
を直接呼び出すことができませんが、forEach
については実装されており、呼び出すことができます。
<body>
<div class="foo">a</div>
<div class="foo">b</div>
<div class="foo">c</div>
</body>
const nodeList = document.querySelectorAll('.foo')
console.log(nodeList)
NodeList(3) [div.foo, div.foo, div.foo]
0: div.foo
1: div.foo
2: div.foo
length: 3
[[Prototype]]: NodeList
prototype
がArray.prototype
ではなく、NodeList.prototype
となっていることがわかります。
NodeList.prototype
には以下のようなプロパティが含まれています。
[[Prototype]]: NodeList
entries: ƒ entries()
forEach: ƒ forEach()
item: ƒ item()
keys: ƒ keys()
length: (...)
values: ƒ values()
constructor: ƒ NodeList()
Symbol(Symbol.iterator): ƒ values()
Symbol(Symbol.toStringTag): "NodeList"
get length: ƒ length()
[[Prototype]]: Object
確かにforEach
が存在しています。
map
やfilter
は見当たらず、使用しようとするとエラーとなります。
文字列
文字列はプリミティブな値ですが、Array-like な振る舞いをします。
length
プロパティをもち、数値インデックスによって文字へアクセスが可能なためです。
また、文字列に対してもforEach
やmap
、filter
などを呼び出すことはできません。
配列メソッドを使うには
Array.from()
を使う方法
Array.from()
はArray-like object や iterble object を配列にすることができます2。
イテレート可能でないオブジェクトを配列に変換するためには、length
プロパティを持つ必要があります3。
先の例でのnodeList
をArray.from()
の引数として渡すと、Array.prototype
をもつ配列が返されます。
const nodeList = document.querySelectorAll('.foo')
const nodeListArray = Array.from(nodeList)
console.log(nodeListArray)
(3) [div.foo, div.foo, div.foo]
0: div.foo
1: div.foo
2: div.foo
length: 3
[[Prototype]]: Array(0)
NodeList
や文字列に対してmap
やfilter
を使いたい場合には、配列へと変換すればいいわけですね。
Function.call()
で呼び出す方法
Function.call()
はオブジェクトのメソッドを他のオブジェクトに割り当てて使用できるようにするメソッドです。
Function.call()
を使用すれば、配列に変換せずに、Array.prototype
から間接的にmap
やfilter
を呼び出すことも可能です。
第1引数にはthis
として使われるオブジェクトを指定し、第2引数以降には呼び出す関数への引数を指定します。
Array.prototype.filter
を使いたいときには、第1引数にArray-like object、第2引数にfilter
関数への引数を渡して使用します。
例
const foo = {}
for(let i=0; i<3; i++){
foo[i] = i;
}
foo.length = 3;
const bar = Array.prototype.filter.call(foo, (fooProperty)=>fooProperty >= 1)
console.log(bar)
[1, 2]
この例ではfoo
というArray-like object にfilter
を間接的に呼び出して適用しています。
Array-like object にfilter
を適用したことで、1以上の要素のみに絞り込まれた配列が返ってきていることがわかります。