【JavaScript】一旦初心に戻ってDOM操作を学び直す part02


なぜ学び直すのかとその目的

getElementByIdとか、querySelectorとかを使う簡単なDOM操作には結構慣れてきました。
でも、複数のDOM操作をする場合にあまり効率よくできてないんじゃないかな?
と思うことがあります。itemやらバブリングやらを使いこなして、スッキリしたコードをかけるようになるべく再入門です。

【今日のメインテーマ】
・イベント名の似ているややこしい違いについて
・属性名の取得って色々方法あるね

前回→https://qiita.com/irico/items/18471dd3260f1297cabb
(HTMLコレクションとNodeコレクション、ノードウォーキングについて)

【参考】
https://codeday.me/jp/qa/20190201/205623.html
・書籍「JavaScript本格入門」p293~p302

イベントドリブンモデル

【基本のおさらい】
JavaScriptでよく行われる、ボタンが押された→イベント発生のようなプラグラミングモデルを
イベントドリブンモデルと言います。

そしてイベントの処理内容を定義している関数をイベントハンドラーと呼びます。

わりにくいイベントまとめ

mouseupclickとか、何がどのタイミング?がわかりにくいイベントが多いのでまとめておきます。

マウスクリック系のイベント

mousedown・・・要素上でマウスを押した時発生
mouseup ・・・要素上でマウスを離した時
click・・・要素上でマウスをクリックした時

mouseup ≠click です。
例えば、関係のない要素でマウスを押し、ターゲットの要素でマウスを離した場合、
mouseupは実行され、clickは実行されません。絶妙ですね。

マウス移動系のイベント

mouseenter/mouseleave・・・対象の要素の出入り時に発生
mouseover/mouseout ・・対象の要素の出入り時 + 内側の要素の出入り時にも発生

ちょっとわかりにくいのでコード付きで説明します。

html
    <div class="parent">
        <div class="child"></div>
    </div>
css
.parent{
  margin: 0 auto;
  display: flex;
  justify-content: center;
  align-items: center;
  width: 400px;
  height: 400px;
  background: black;
}
.child{
  width: 200px;
  height: 200px;
  background: white;
}
js
let target = document.querySelector('.parent');

target.addEventListener('mouseover',()=>{
    console.log('入った!');
})
target.addEventListener('mouseout',()=>{
    console.log('でた!');
})

.child.parentで囲み、.parentmouseover/mouseoutのイベントを付与しています。

↑こんな感じになる訳ですが、これをマウスで左端の白いとこから右端の白いところまでスーッと移動させると、

コンソールではこのようになります。
子要素に出入りする際も入った!でた!が表示されるようになるんですね。
(詳しく言うと、親要素から出るとき、子要素から出るとき共にmouseoutが働く
親要素に入るとき、子要素に入るとき共にmouseenterが働く)
mouseenter/mouseleaveだと子要素には反応しません。

間違えやすそうなイベント定義注意点

プロパティとして設定するときは、関数オブジェクト!(関数呼び出しではない!)


window.onclick = event();   //ダメな例

window.onclick = event; //正しい

うっかり間違えそうですね。

プロパティとして設定するときは、1つの要素に登録できるイベントリスナーは1つだけ!

onXXXXイベント系の制約です。addEventListenerを使いましょう。

イベントハンドラーはDOMContentLoadedイベントハンドラーの配下におく!

DOMContentLoadedイベントハンドラー配下におくことで、画面全体のロードが終わってからイベントが実行できるようにします。
基本的にjsはbody閉じタグ直前におくので、要素が取得できない!
と言うことはないかもしれませんが、習慣づけることは大切です。

(ただし、重たいサイトで最初のページの方のイベント、とかだったら話が変わるかも。臨機応変に)
DOMContentLoadedと似たものでonloadイベントがありますが、こちらは
画像を含む読み込みが終わったタイミングで実行されます。
DOMContentLoadedは画像の読み込みを待ちません。

画像読み込みを待たなければそれだけ早く実行できるので、
画像の読み込み必要→onload
画像読み込み必要ない→DOMContentLoadedで使い分けてください。

属性の取得における属性名≠取得名問題

hrefを設定したければ

element.href ="http:~"

と、大体が一致しているのですが、
一部属性名≠取得名のものも存在します。
有名なものではclassがありますね。

element.className ='section'

私は普段これでアクセスしてましたが、
getAttributesetAttributeで取得すると、この不一致問題が解消され、属性名を動的に変更可能という良さがあるみたいです。

let myHref = link.getAttribute('href');  //取得
myHref.setAttribute('href','http:~');   //設定

コードが長くなるので、これも臨機応変ですね。

不特定の属性の取得

attributes・・・特定の要素ノードの全ての属性を取得する。
全属性の戻り値ですが、NamedNodeMapオブジェクトで返されます。初めて聞いた...
こいつはHTMLCollectionに似ていて、個別のノードに名前でもインデックス番号でもアクセスできちゃいます。
ただし、HTMLCollectionなどと違ってノードの順序を保守してないことに注意です。

NamedNodeMap.item(i).nameで属性名、
NamedNodeMap.item(i).valueで属性の値を取得できます。

document.addEventListener('DOMContentLoaded',()=>{
    let target = document.querySelector('.target');
    let attrs = target.attributes;   //属性リストを取得
    for(let i = 0;i < attrs.length;i++){
        let attr = attrs.item(i);
        console.log(attr.name + ':' + attr.value);
    }
},false);

このコードは全ての属性をコンソールで列挙しています。
addEventListenerの第三引数falseがきになったらこちらをどうぞ。
https://qiita.com/irico/items/8a23071719aa28e3c032
https://qiita.com/irico/items/3e3cd9fad4610b83cf87

NamedNodeMapは属性の付け外しも思いのまま!

//つけるとき
let title = document.createAttribute('title')
title.value = '画像です'
attrs.setNamedItem(title);
//外すとき
attrs.removeNamedItem('title');