概要
javascriptでmapメソッドを使いたかったが以下のエラーが出てきたのでメモ。
配列っぽいデータを加工したい場合はmapメソッドを使っとけばいい、と思っていたが、それは曖昧な理解ですべての場合で使えるわけではなかったので、問題の解決とともにしっかりと掘り下げてみた。
Uncaught TypeError: x.map is not a function
今回やろうとしたこと
inputタグを利用してファイルを複数選択できるようにし、そのファイル群をmapメソッドで処理するというもの。
ただ、今回のケースと違っていても同じ考え方で解決できる問題はあるはず。
<html>
<body>
<input type="file" multiple onchange="selectFile()" id="file"/>
</body>
</html>
わかったこと
##なぜmapが使えなかったか。
そもそも私が何気なく利用していたmapはArray.prototype.map()
というものであった。
Array.prototype
ってなに、となるがこれはプロトタイプチェーンや型の構造について理解すれば解決する。この記事が理解の助けになった。
mapメソッドを利用するためには操作対象のオブジェクトがmapメソッドを持つ、もしくは__proto__
プロパティがmapメソッドを持つ必要がある。Array型のオブジェクトは__proto__
プロパティにmapメソッドを持つためmapをすることができる。このことが前述の
私が何気なく利用していたmapは
Array.prototype.map()
というものであった。
という部分を説明してくれている。
今回私が操作したいデータをみてみると以下のデータであり、Arrayではないし、FileListの__proto__
プロパティにmapメソッドが定義されていないためmapが使えないのも納得である。しかしFileListは頑張れば繰り返し処理をさせられそうな形である。であれば、どうすればmapを利用可能にできるかが知りたい。
FileList {0: File(147802), 1: File(370312), length: 2}
とりあえず調べてみるとstackoverflowにそれらしいことが書いてあったので一部を引用する。
FileList is not an Array, and does not inherit from Array, but it does implement the iterable protocol, so you can use the spread syntax to get it as an array.
FileListはiterable protocol
を実装しているらしい。そしてspread syntax
を使えばArrayとしてデータを取ってこられるらしい。私にはiterable protocol
とspread syntax
がわからなかったが、この2つの単語を理解できれば解決もすぐそこだと思うので、もう少し調べることにした。
iterable protocol
とはなんなのか。
MDN - 反復処理プロトコルの抜粋だが下記のように述べられている。
iterable (反復可能)プロトコルによって、JavaScriptオブジェクトは、for..of構造で値がループしているもの等の反復動作を定義、または、カスタマイズできます。ArrayやMapのように、いくつかのビルトインの型はデフォルトの反復動作を持っているビルトイン iterables です。一方、(Objectのような)他の型は反復動作を持ちません。
簡単に言うとiterable protocol
を実装していることは反復可能、であるということがわかる。同記事の反復可能オブジェクトを期待する構文に述べられているが、以下の方法でiterable protocol
を実装したオブジェクトの値を取得することができる。
// 例1
for(let value of ["a", "b", "c"]){
console.log(value);
}
// "a"
// "b"
// "c"
// 例2
[..."abc"]; // ["a", "b", "c"]
この例2を見てみると[...変数]
の形式を利用すると配列を取得できることがわかる。
これによりIterable protocol
を実装しているがArrayオブジェクトではないFileListオブジェクトはArrayオブジェクトに無事に変換できることがわかる。
spread syntax
とはなんなのか
MDN - スプレッド構文を見ると明らかになったが、先の「iterable protocol
とはなんなのか。」で登場した例2のコードがまさにこれである。3点リーダーで変数を展開することをspread syntax
という、これだけのことである。
これで以下の文言をしっかりと理解することができた。
FileList is not an Array, and does not inherit from Array, but it does implement the iterable protocol, so you can use the spread syntax to get it as an array.
そして私はこれを駆使して無事にFileList型からmapメソッドを利用できるようになった。
その他
Iterableか確認する
これも先ほどから抜粋しているstackoverflowで述べられているが操作対象のオブジェクトがIterableであるかは
変数名[Symbol.iterator]()
で確認することができる。
参考
https://stackoverflow.com/questions/44511925/how-to-map-through-an-array-of-input-files
https://qiita.com/howdy39/items/35729490b024ca295d6c
http://lightbulbcat.hatenablog.com/entry/2018/02/04/060811
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Iteration_protocols#Syntaxes_expecting_iterables
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Spread_syntax