はじめに
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以上の要素のみに絞り込まれた配列が返ってきていることがわかります。