前回の投稿(document.getElementsByClassNameでうまく取得できない…)でgetElementsByClassNameで取得した値を、forEachで回した時に、意図した値が返ってこない原因について、@cfm-art様に凄く分かりやすく教えていただいたので、忘れる前にメモ!!
@cfm-art様ありがとうございます
getElementsByClassNameで意図しない値が返ってきた
MODAL.methods = {
show: function(elm){
var $self = elm;
var $next = $self.nextElementSibling;
if($next.classList.contains(MODAL.tar) && !$self.classList.contains(MODAL.active)) {
$self.classList.add(MODAL.active);
$next.classList.add(MODAL.active);
$next.style.display = 'block';
console.log(document.getElementsByClassName(MODAL.active));
MODAL.methods.overlay();
}
},
close: function(){
var $close = document.getElementById(MODAL.close);
console.log(document.getElementsByClassName(MODAL.active));
Array.prototype.forEach.call(document.getElementsByClassName(MODAL.active), function(elm){
elm.classList.remove(MODAL.active);
if(elm.classList.contains(MODAL.tar)){
elm.style.display = 'none';
}
});
$close.remove();
},
overlay: function(){
console.log(document.getElementsByClassName(MODAL.active));
document.body.insertAdjacentHTML('beforeend', '<div id="' + MODAL.close + '" class="modal-overlay" onClick="MODAL.methods.close();"><span class="modal-overlay_child">close</span></div>');
}
};
getElementsByClassNameで値を取得した際の各メソッドのlengthの返り値
※lengthの値が2で返ってきてほしい
- MODAL.methods.show = length: 2
- MODAL.methods.overlay = length: 2
- MODAL.methods.close = length: 1 ←( ^ω^)・・・
MODAL.methods.closeだけlengthが1で返ってくる…
しばらく調べてみたものの理由が分からないのでQiitaで質問して、詳しく説明していただきました!
document.getElementsByClassNameの返却値は配列ではなく「HTMLCollection 」というものです。
これは大カッコを使って配列のようにアクセス出来る 配列様 オブジェクトですが、配列ではありません。
MDNには拙い翻訳で以下のように書かれています。
https://developer.mozilla.org/ja/docs/Web/API/HTMLCollection
HTML DOM内のHTMLCollection は生きて(live)います
それらは元になったdocumentが変更された時点で自動的に更新されます.
以下のようなコードを試してみるとその意味がよく分かるかと思います。
/*
const list = document.getElementsByClassName(MODAL.active);
for (let i = 0; i < list.length; ++i) {
list[i].classList.remove(MODAL.active);
}
> 処理の流れとしては
> ```js
const list = document.getElementsByClassName(MODAL.active);
// for の中身
// forの1週目
let i = 0;
i < list.length; // 0 < 2
list[i].classList.remove(MODAL.active); // lengthが1減る
// forの2週目
++i; // 0 → 1
i < list.length; // 1 < 1 でループを抜ける
となっており、思った結果になっていないと推察できます。
原因は「HTMLCollection」が動的にclassを参照していたことが原因で、ループ中に参照しているclassを削除するとHTMLCollectionの返り値が変化してしまって意図しない結果になってました。
解決策
getElementsByClassNameをquerySelectorAllに変更!
HTMLCollectionを返すgetElementsByClassNameではなく、
静的なNodeListを返してくれるquerySelectorAllにすることで解決しました!
MODAL.methods = {
show: function(elm){
var $self = elm;
var $next = $self.nextElementSibling;
if($next.classList.contains(MODAL.tar) && !$self.classList.contains(MODAL.active)) {
$self.classList.add(MODAL.active);
$next.classList.add(MODAL.active);
$next.style.display = 'block';
console.log(document.getElementsByClassName(MODAL.active));
MODAL.methods.overlay();
}
},
close: function(){
var $close = document.getElementById(MODAL.close);
console.log(document.querySelectorAll('.' + MODAL.active));
Array.prototype.forEach.call(document.querySelectorAll('.' + MODAL.active), function(elm){
elm.classList.remove(MODAL.active);
if(elm.classList.contains(MODAL.tar)){
elm.style.display = 'none';
}
});
$close.remove();
},
overlay: function(){
console.log(document.getElementsByClassName(MODAL.active));
document.body.insertAdjacentHTML('beforeend', '<div id="' + MODAL.close + '" class="modal-overlay" onClick="MODAL.methods.close();"><span class="modal-overlay_child">close</span></div>');
}
};
querySelectorAllの返り値についてMDNにこう記載されています
document.querySelectorAll
https://developer.mozilla.org/ja/docs/Web/API/Document/querySelectorAll
返り値
指定されたセレクタの少なくとも 1 つにマッチする要素について 1 つの Element オブジェクトを含む、生きていない NodeList。
NodeList
https://developer.mozilla.org/ja/docs/Web/API/NodeList
対して、DOM への変更が NodeList オブジェクトの内容に影響を与えないものもあります。document.querySelectorAll() メソッドは、そのような静的な NodeList を返します。
※最後に
分からなかったとはいえ、Qiitaで質問してしまい本当に申し訳ないです…
以後気を付けます!
ご指摘ありがとうございました!