NodeListをforEachしたいときのパターン
document.querySelectorAllとかで取得したNodeListに対してループ処理をしたいということがよくある。
NodeListはArrayに似ているのでforEachとかを使いたくなるけど、それらは似て非なるものなので無理だ。
単純にforでなら回せるけどそれは嫌なので、他の方法はないかと調べると結構いろんなやり方があった。
まず単純にfor文で回すパターン。
for (var i = 0, len = NodeList.length; i < len; i++) { console.log(NodeList[i]); }
for文で回すラッパー関数を作るパターン。
var each = function (NodeList, iteratee) { for (var i = 0, len = NodeList.length; i < len; i++) { iteratee(NodeList[i], index, NodeList); } }; each(NodeList, function (el, i) { console.log(el, i); });
forEachのthisを偽装するパターン。
Array.prototype.forEach.call(NodeList, function (node) { console.log(node); });
Arrayに変換するパターン。
Array.prototype.slice.call(NodeList).forEach(function (el) { console.log(el); });
NodeListのprototypeを拡張するパターン(forEachだけ)。
NodeList.prototype.forEach = Array.prototype.forEach; NodeList.forEach(function (el) { console.log(el); });
NodeListのprototypeを拡張するパターン(全部)。
Object.getOwnPropertyNames(Array.prototype).forEach(function (methodName) { if (methodName !== 'length') { NodeList.prototype[methodName] = Array.prototype[methodName]; } });
Object.keysを使うパターン。
Object.keys(NodeList).forEach(function (key) { console.log(NodeList[key]); });
for...ofで回すパターン。
for...ofはES6の仕様で、Firefoxなら使えるけど他のブラウザはまだ対応してないので、babelをポリフィル付きで使わないといけない。
for (let node of NodeList) { console.log(node); }
ちなみにjQueryとlodashの実装を見てみると、どっちも内部的にforを回していた。
個人的には、prototypeを拡張するのは嫌で、sliceでArrayに変換するのも無駄なことをしているような気がするので、forで回すラッパー関数を作ってやるのがきれいだと思う。
追記(11/18)
NodeListにforループをまわすときは、Arrayに変換してからやるほうがパフォーマンスがいいらしい。
NodeListはライブオブジェクトなのでパフォーマンス的に不利、とパーフェクトJavaScriptに書いてた。
実際にchromeで試してみるとだいたい2倍くらい速かったけど、本に書いてるサンプルと処理速度が全然違って(10-100倍くらい)、今の時代そこまで気にしないでいいのかなと思った。
querySelectorとquerySelectorAllというかLive NodeListとStatic NodeList - MOL
参考
NodeList - Web API インターフェイス | MDN
Loop Over querySelectorAll Matches | CSS-Tricks