【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でよく行われる、**ボタンが押された→イベント発生**のようなプラグラミングモデルを
`イベントドリブンモデル`と言います。

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

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

###マウスクリック系のイベント
`mousedown`・・・要素上でマウスを押した時発生
`mouseup` ・・・要素上でマウスを離した時
`click`・・・要素上でマウスをクリックした時

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

###マウス移動系のイベント
`mouseenter/mouseleave`・・・対象の要素の出入り時に発生
`mouseover/mouseout`　・・対象の要素の出入り時　＋　内側の要素の出入り時にも発生

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

```html:html
    <div class="parent">
        <div class="child"></div>
    </div>
```
```css: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:js
let target = document.querySelector('.parent');

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

`.child`を`.parent`で囲み、`.parent`に`mouseover/mouseout`のイベントを付与しています。
![スクリーンショット 2019-08-29 14.18.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/441432/82ccb39e-78c5-e7c5-4ed0-f948a95ffc09.png)
↑こんな感じになる訳ですが、これをマウスで左端の白いとこから右端の白いところまでスーッと移動させると、
![スクリーンショット 2019-08-29 14.18.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/441432/a8af472f-555e-fc8a-351b-67f0c44ac8c2.png)

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

##間違えやすそうなイベント定義注意点
###プロパティとして設定するときは、関数オブジェクト！（関数呼び出しではない！）

```javascript

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

window.onclick = event; //正しい
```
うっかり間違えそうですね。

###プロパティとして設定するときは、１つの要素に登録できるイベントリスナーは１つだけ！
onXXXXイベント系の制約です。`addEventListener`を使いましょう。

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

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

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

##属性の取得における属性名≠取得名問題
hrefを設定したければ

```js
element.href ="http:~"
```
と、大体が一致しているのですが、
一部属性名≠取得名のものも存在します。
有名なものではclassがありますね。

```javascript
element.className ='section'
```

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

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

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

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

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

```js
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は属性の付け外しも思いのまま！

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