LINDORのAdvent Calendar(本物)の11日目を開けたところ。
配列とかおれおれAdvent Calendar2018 – 11日目

querySelectorAll() で取ってきた結果は NodeList という配列風オブジェクトだけど、配列ではありません。

つまり、配列が持つ各種のメソッドは使えません。 使いたい場合は配列へ変換してやる必要があります。

しか forEach() については近年使えるようになりました。

const els = document.querySelectorAll('.target');
els.forEach((el) => {
  console.log(el.textContent);
});

中身は配列の forEach() と完全に同じです。

配列ではないことの確認

const els = document.querySelectorAll('.target');
console.log(els.length, els[0]);
console.log(Array.isArray(els)); // => false
console.log(els.constructor.name); // => "NodeList"

仕様

現在のLiving Standardの記述は、こう。

[Exposed=Window]
interface NodeList {
  getter Node? item(unsigned long index);
  readonly attribute unsigned long length;
  iterable<Node>;
};

この iterable はWebIDLのやつです。よね? そっちで forEach() が定義されてます。

If the interface defines an indexed property getter, then the Function object is the initial value of the “forEach” data property of %ArrayPrototype% ( [ ECMA-262] , section 6.1.7.4).

インターフェイスがindexed property getterを定義している場合、その関数オブジェクトは%ArrayPrototype% ( [ ECMA-262] , section 6.1.7.4) の “forEach” の初期値である。

実際そうなってる。

console.log(NodeList.prototype.forEach === Array.prototype.forEach); // => true

利用可能な環境

全てのウェブブラウザーで利用可能です。(IEはもはやブラウザーではない。)

IE以外は全て対応済み。

対応していない場合

いろいろPolyfillあるはずなんで探してください。

その他のメソッド

以下も利用可能です。

  • entries()
  • keys()
  • values()

配列のメソッドを利用するなら call()

8日目に書いたんだけど、配列のメソッドは配列風オブジェクトで動くように設計されています。

というわけで、 call() しよう。

const els = document.querySelectorAll('.target');
const texts = Array.prototype.map.call(els, (el) => el.textContent);
console.log(texts);

配列へ変換するなら [...els]

場合によってはいっそ変換してしまう方が楽かも。

色々やり方はあるけれど、スプレッド構文 ... が一番楽かなと思います。

const els = document.querySelectorAll('.target');

const arr = [...els];

console.log(arr.length, arr[0]);
console.log(arr instanceof Array); // => true
console.log(arr.constructor.name); // => "Array"

例

短いので [...els] の方で書きました。

フォームのテキスト入力値を全て取得する例

そういう用途だと map() が便利です。

<input type="text" class="text-input" value="Apple">
<input type="text" class="text-input" value="Banana">
<input type="text" class="text-input" value="Orange">
const elInputList = document.querySelectorAll('.text-input');
const values = [...elInputList].map((el) => el.value);
console.log(values); // => [ "Apple", "Banana", "Orange" ]

これはテキスト入力だけだけど、中で el.type を見て分岐したりすればもっと細かく各種取得できますね。チェックボックスを配列にしたりってのまで考えると reduce() の方が良いかもな。

近年使えるようになりました?

いつからだっけ?

まだ使えなかった頃の質問。

MDNの互換性の表によるとChrome 51からだそうで、そうなると2016年5月のようです。

結構前だなー。

おしまい

forEach() があるだけで相当便利です。

map() も使いたいけど、 NodeList を map() した結果が NodeList にならないのも気味が悪い気がするし、まあ仕方ないかなあ。

関連

参考