---
# System prepended metadata

title: WebAPI

---

WebAPI
===

## API( Application Programming Interface )
> - 簡單說就是別人寫好的function給你調
### Web API
> - 瀏覽器提供操作瀏覽器的 API ( BOM & DOM )


## DOM ( Document Object Model )
> - [W3C](https://www.w3.org/) 推薦的 WebAPI 編程街口
> - 把文檔當成對象來處理(增刪改查, 屬性操作, 事件操作), 處理後的結果返回到頁面中
> - 使 HTML 形成一顆 DOM 樹, 包含 text, elem, node
> - 要求: 文檔必須在記憶體中

> #### 創建元素
> - document.write()
> - innerHTML
> - createElement()

> #### 增
> - appendChild()
> - insertBefore()

> #### 刪
> - removeChild()

> #### 改
> - 修改元素屬性: node.attr(proprety?)
> - 修改文本內容: innerHTML, innerText, textContent
> - 修改表單元素: value, type...
> - 修改樣式: style, className

> #### 查
> - 舊方法: getElementById, getElementsByTagName, getElementByClass...
> - H5 新方法:  querySelector, queySelectorAll
> - Node: parentNode, children, previousElementSibling, nextElementSibling

> #### Attr 操作
> - getAttribute
> - setAttribute
> - removeAttribute

> #### 事件操作
> - 鼠標事件
>   - onclick : 點擊
>   - onmouseover : 滑鼠經過
>     - mouseover 的 relatedTarget 屬性掛載了從哪個 DOM 來的
>   - onmouseout : 滑鼠離開
>     - onmouseout 的 relatedTarget 屬性掛載了離開去到哪個 DOM
```htmlembedded=
<body>
    <style>
        .fa {
            padding: 100px;
            background: blue;
        }
        .son {
            height: 100px;
            width: 100px;
            background: red;
        }
    </style>
    <div class="fa">
        <div class="son"></div>
    </div>
    <script>
        const oFa = document.querySelector('.fa')
        oFa.onmouseover = ev => console.log('onmouseover: 從', ev.relatedTarget, '離開, 進到: ', ev.target)
        oFa.onmouseout  = ev => console.log('onmouseout: 從', ev.target, '離開，進到', ev.relatedTarget)
    </script>
</body>
```


> - 鼠標事件
>   - onfocus : 獲得焦點
>   - onblur : 失去焦點
>   - onmousemove : 滑鼠移動
>   - onmouseup : 滑鼠彈起
>   - onmousedown : 滑鼠按下

### HTML Tree
> - 節點: 網頁所有內容都是節點(標籤, 屬性, 文本, 註釋...)
>   - 元素: 文檔中的標籤
>   - 屬性: 標籤中的屬性
```
Doctument
  - <html>: Root element
  
    - <head>: Element
      - <title>: Element
        - ' ': text
      - ...
      
    - <body>
      - <a>: Element
        - href: Attribute
      - <p>: Element
        - ' ': text
      - ...
```

## 獲取元素
### 根據 ID 獲取
> - `document.getElementById(id)`
```javascript=
var main = document.getElementById('main');
console.log(main);
```
> #### 觀念釐清
> - 由於執行code是從上往下, 既然沒有先讀到標籤, 當然JS也無法找到該標籤而指向null
> - 所以 JS 應該是寫在下面
```htmlembedded=
<!-- 
-->
<head>
  <meta charset="utf-8">
  <script>
    var main = document.getElementById('main');
    console.log(main); // null
  </script>
</head>
<body>
  <div id='main'>123</div>
</body>
```

```htmlmixed=
<head>
  <meta charset="utf-8">
</head>
<body>
  <div id='main'>123</div> // 
  
  <script>
  var main = document.getElementById('main');
  console.log(main); <!-- <div id="main">123</div> 
                          這不是 str, 而是對象-->
  <!-- 因為console.log() 看不清楚他是不是對象
       此時可以使用console.dir() 來專門輸出對象 -->
  console.dir(main); <!-- div#main -->
    
  </script>
</body>
```
> #### 查看類型
> - __proto__: 類型
```htmlmixed=
<head>
  <meta charset="utf-8">
</head>
<body>
  <div id='main'>123</div>
  <p id='p'>456</p>

  <script>
  var str = new String('haha');
  var main = document.getElementById('main');
  var p = document.getElementById('p');

  console.log(str); // __proto__: String 
  console.dir(main); // __proto__: HTMLDivElement
  console.dir(p); // __proto__: HTMLParagraphElement
  </script>
</body>
```

### 根據標籤名
`document.getElementsByTagName(name)`
> - `document.getElementsByTagName()` 返回的類型為 HTMLCollection
> - HTMLCollection 
>   - 動態集合
>   - 偽陣列 ( 有位置下標(1 2 3), 看起來像陣列的對象 )
>     - 由於他有索引跟length, 所以也可以像陣列那樣遍歷元素
> - 集合裡的元素的類型都是該標籤對應的類型 div -> HTMLDivElement 
```htmlmixed=
<body>
  <div id='main'>123</div>
  <div>456</div>
  <div>789</div>

  <script>
  var divs = document.getElementsByTagName('div');
  console.log(divs); 
    // HTMLCollection(3) [div#main, div, div, main: div#main]
    // 0: div#main
      // __proto__: HTMLDivElement
    // 1: div
      // __proto__: HTMLDivElement
    // 2: div
      // __proto__: HTMLDivElement
    // length: 3
    // main: div#main
      // __proto__: HTMLDivElement 
    // __proto__: HTMLCollection
  </script>
</body>
```
> - 動態集合 
>   - 當瀏覽器讀取到標籤時, 集合也會動態添加
```htmlmixed=
<head>
  <meta charset="utf-8">
  <script>
  var divs = document.getElementsByTagName('div');
  console.log(divs) // HTMLCollection []
                      // 0: div#main
                      // 1: div
                      // 2: div
                      // length: 3
                      // main: div#main
                      // __proto__: HTMLCollection
  console.log(divs.length); // 0
  </script>
</head>
<body>
  <div id='main'>123</div>
  <div>456</div>
  <div>789</div>

  <script>
  console.log(divs); // 3
  console.log(divs.length) // HTMLCollection(3) [div#main, div, div, main: div#main]
  </script>
</body>

<!-- 
  HTMLCollection 一開始的確是 [], 長度也是0
  展開的那些東西都是後來動態添加進去的
  
  ps. 當 JS 找不到該標籤時, 屬性 length: 0
-->
```
> - 選取區塊內的元素
>   - `getElementById` 是找到那個Id的元素
>   - `getElementsByTagName` 是找到那個標籤名的所有元素集合
>   - 那選指定區塊的元素就可用 `getElementById.getElementsByTagName` 來取得
```htmlmixed=
<body>
  <div id='main'>
    <div>456</div>
    <div>789</div>
  </div>
  <div id='unmain'>
    <div>456</div>
    <div>789</div>
  </div>

  <script>
  var main = document.getElementById('main'); // 獲取main的對象
  var mainDiv = main.getElementsByTagName('div'); // 獲取main裡面的div
  console.log(mainDiv); // HTMLCollection(2) [div, div]
  
  // 對比 document 直接取, 會把全部都取下來
  console.log(document.getElementsByTagName('div'));
    // HTMLCollection(6) [div#main, div, div, div#unmain, div, div, 
    //                    main: div#main, unmain: div#unmain]
  </script>
</body>
```

### 根據 name 獲取元素
`getElementsByName`
> - 根據 name 屬性來取
> - IE10 跟以前的
>   - 會連同名的 id 一起取回來, 
>   - 且 Edge 跟 IE 返回的類型是 HTMLCollection 而不是 NodeList
```htmlmixed=
<body>
  <div id='main'>12</div>
  <div name='main'>34</div>

  <script>
  var divs = document.getElementsByName('main');
  console.log(divs); // NodeList [div]
                       // 0: div
                       // length: 1
                       // __proto__: NodeList
  </script>
</body>
```

### 根據 class 獲取元素
`getElementsByClassName(names)`
> - 兼容問題: IE9以後才支援
```htmlmixed=
<body>
  <div id='main'>12</div>
  <div name='main'>34</div>
  <div class='main'>34</div>

  <script>
  var divs = document.getElementsByClassName('main');
  console.log(divs); // HTMLCollection [div.main]
                       // 0: div.main
                       // length: 1
                       // __proto__: HTMLCollection
  </script>
</body>
```


### 根據選擇器獲取元素
`querySelector()`
> - 只取第一個匹配到的

`querySelectorAll()`
> - 所有匹配到的

> 兼容性問題: IE8才支援
```htmlmixed=
<body>
  <div id='main'>12</div>
  <div name='main'>34</div>
  <div class='main'>34</div>
  <div class='main'>56</div>

  <script>
  var elem = document.querySelector('.main');
  console.log(elem); // <div class='main'>34</div>
  var elem2 = document.querySelector('#main');
  console.log(elem2); // <div id='main'>12</div>
  var elems = document.querySelectorAll('.main');
  console.log(elems); // NodeList(2) [div.main, div.main]
                        // 0: div.main
                        // 1: div.main
                        // length: 2
                        // __proto__: NodeList
  </script>
</body>
```


## 事件
> - 觸發響應機制
> - 事件三元素:
>   - 事件源
>   - 事件名稱
>   - 事件處理程序

點擊前
![](https://i.imgur.com/fQ7C8lt.png)

點擊後
![](https://i.imgur.com/b4gyrjn.png)


```htmlmixed=
<body>
  <button id="butn">點我啊笨蛋</button>
  <div id='divv'>點我啊笨蛋</div>
  <script>
  var butn = document.getElementById('butn');
  var divv = document.getElementById('divv');
  butn.onclick = function () { 
    console.log('我是button')
  }
  divv.onclick = function () {
    console.log('我是divv')
  }
  // butn, divv: 事件源; 
  // click: 事件名稱;
  // function: 事件處理程序
  </script>
</body>
```
> ### 練習: 點擊按鈕 切換圖片
> - 標籤是屬於HTML的, 標籤的屬性也是屬於HTML的
> - DOM 的元素一般情況下是封裝了對應的標籤屬性
```htmlmixed=
<body>
  <button id="butn"></button>
  <img id="testP" src='https://api.fnkr.net/testimg/100x100'/>

  <script>
  var butn = document.getElementById('butn');
  var testP = document.getElementById('testP');

  var flag = 1; // 當flag=1時, 圖片100x100; flag=2時, 圖片100x50;
  butn.onclick = function () {
    if (flag===1) {
      flag = 2;
      // DOM屬性對應標籤屬性, 所以直接改屬性就好了
      testP.src = 'https://api.fnkr.net/testimg/100x50';
    } else if (flag===2) {
      flag = 1;
      testP.src = 'https://api.fnkr.net/testimg/100x100';
    }
  }
  </script>
</body>
```


## 屬性操作
### 非表單元素屬性
> - href, src, title, id
```htmlmixed=
<body>
  <img id="testP" src='https://api.fnkr.net/testimg/100x100'/>
  <a id="goo" href="https://www.google.com" title="Google">Google</a>
  <a href="https://www.google.com">沒改過的Google</a>

  <script>
  var testP = document.getElementById('testP');
  var goo = document.getElementById('goo');

  // 獲取DOM對象屬性值
  console.log(testP.id, testP.src);
    // testP https://api.fnkr.net/testimg/100x100
  console.log(goo.id, goo.title, goo.href);
    // goo Google https://www.google.com/


  // 對DOM對象屬性值操作
  goo.href = 'https://www.yahoo.com';	
  testP.src = 'https://api.fnkr.net/testimg/100x50';
  </script>
</body>
```
> - className
>   - DOM 對應的標籤 ==**class 屬性名為 className**==
>     - 因為class是關鍵字,
>     - 關鍵字不可以做為變量或屬性名

> - 雖然分類非表單元素屬性, 但這只是表示非特有, 
>   亦即表單元素也會有某些屬性, 如 (id ,className)

> #### this 整理
>   - 普通函數中的 this: Window
>   - 構造函數中的 this: 實例對象 (創誰指誰)
>   - 方法中的 this: 對象方法所屬 (誰調指誰)
>   - 事件處理函數中的 this: 事件源 (誰調指誰)
>     - 例如 `butn.onclick` 的 this 就指向 butn
>       - `butn.className` = `this.className`
```htmlmixed=
<head>
  <meta charset="utf-8">
  <style>
  div {
    height: 100px;
    width: 100px;
    background: skyblue;
  }
  .show {
    display: block;
  }
  .hidden {
    display: none;
  }
  </style>
</head>
<body>
  <input id='butn' type="button"value="顯示"/>
  <div id='divv' class='show'></div>

  <script>
  var butn = document.getElementById('butn');
  var divv = document.getElementById('divv');

  var flag = 1;
  butn.onclick = function () {
    if (flag === 1) {
      flag = 2;
      // divv.class='hidden'; 搞半天不叫 class == 
      divv.className='hidden';
      // butn.value = '隱藏'
      this.value = '隱藏'; // this 指向事件源, 也就是 butn
    } else if (flag === 2) {
      flag = 1;
      // divv.class='show';
      divv.className='show';
      this.value = '顯示'; 
    }
  }
  </script>
</body>
```
> #### 練習: 點小圖換大圖
```htmlmixed=
<head>
  <meta charset="utf-8">
  <style>
  .hidden {
    display: none;
  }
  .show {
    display: block;
  }
  </style>
</head>
<body>
  <div id='photoArr'>
    <img id='photo1' src='https://api.fnkr.net/testimg/30x30'/>
    <img id='photo2' src='https://api.fnkr.net/testimg/30x30'/>
    <img id='photo3' src='https://api.fnkr.net/testimg/30x30'/>
  </div>
  <img id='photo4' class='hidden' src='https://api.fnkr.net/testimg/100x100'>
  
  <script>  

  var photo1 = document.getElementById('photo1');
  var photo2 = document.getElementById('photo2');
  var photo3 = document.getElementById('photo3');
  var photo4 = document.getElementById('photo4');

  photo1.onclick = function () {
    photo4.src = 'https://api.fnkr.net/testimg/100x100/F8F';
    photo4.className = 'show';
  }
  photo2.onclick = function () {
    photo4.src = 'https://api.fnkr.net/testimg/100x100/E45';
    photo4.className = 'show';
  }
  photo3.onclick = function () {
    photo4.src = 'https://api.fnkr.net/testimg/100x100/A00';
    photo4.className = 'show';
  }
  photo4.onclick = function () {
    this.className = 'hidden';	
  }	
  </script>
</body>
```
> #### 關掉`<a>`的跳轉
> - `<a>` 自帶跳轉到 href, 關掉的方法就是 return false
```htmlmixed=
<body>
  <a id='gooLink' href='https://www.google.com'>Google</a>
  
  <script>
  var gooLink = document.getElementById('gooLink');
  gooLink.onclick = function () {
    console.log('goo')
    return false
  }
  </script>
</body>
```

> #### 練習: 點小圖換大圖2

```htmlmixed=
<body>
  <div id='aImg'>
    <a href='https://api.fnkr.net/testimg/100x100/F00' title='red'>
      <img src='https://api.fnkr.net/testimg/30x30' />
    </a>
    <a href='https://api.fnkr.net/testimg/100x100/0F0' title='green'>
      <img src='https://api.fnkr.net/testimg/30x30' />
    </a>
    <a href='https://api.fnkr.net/testimg/100x100/00F' title='blue'>
      <img src='https://api.fnkr.net/testimg/30x30' />
    </a>
  </div>
  <img id='cgPhoto' src='https://api.fnkr.net/testimg/100x100' /> <br />
  <p id='figcap'>圖片描述</p>



  <script>
  var aImg = document.getElementById('aImg');
  var photos = aImg.getElementsByTagName('a');
  // console.log(photos); 
    // HTMLCollection(3) [a, a, a]
  for (i=0; i<photos.length; i++) {
    var photo = photos[i];
    photo.onclick = function () { // 讓每張小圖註冊 function
      // - 改圖片
      // 點擊後註冊的優點: 一開始打開網頁的速度比較快, 有用到在處理就好了
      var cgPhoto = document.getElementById('cgPhoto');
      // cgPhoto.src = photo.href // ( 註 )  
      // console.log(this);
      cgPhoto.src = this.href 
      
      // - 改圖片描述
      var figcap = document.getElementById('figcap');
      // console.dir(figcap); // 不知道要改什麼的時候, 可以用這個去查屬性名
      figcap.innerHTML = this.title;	
      return false // - 取消跳轉
    }
  }
  
  /* 註: 
   * 這裡使用 photo.href 會永遠顯示藍色,
   * 因為這個function是在 photo 被點擊的時候才調用
   * 所以被點擊前的code應該長這樣
   * var aImg = document.getElementById('aImg');
     var photos = aImg.getElementsByTagName('a');
     for (i=0; i<photos.length; i++) {
       var photo = photos[i];
       photo.onclick
     }
   * 所以 photo 指向的當然是最後一個(藍色)
   * 
   * 所以這裡使用的是 this.href, 因為 this 是指向事件源(按誰指誰)
   */
  
  </script>
</body>
```
> #### innerHTML vs innerText vs textContent
> - innerHTML 跟 innerText 獲取開始標籤跟結束標籤之間的內容
>   - innerHTML 
>     - 如果內容有標籤, 會擷取所有標籤內容
>     - 設置有標籤的內容時, 會以HTML來解析
>     - 這東西會將原本的東西清掉，然後解析新的字串，
>       解析完後create+append，最後渲染
>     - 這個效率比直接操作 DOM 的效率高
>       - 因為這是由瀏覽器自己解析完成了
>       - DOM 是由客戶端的 JS 搞定的
>   - innerText 
>     - 如果內容有標籤, 會過濾標籤, 且會將前後換行及空白都過濾掉
>     - 設置有標籤的內容時, 會將標籤轉成HTML轉義符, 將標籤顯示出來 
>   - textContent
>     - 如果內容有標籤, 會過濾標籤, 但會留換行與空白

> - 選擇障礙: 要操作inner時, 要用哪個？
>   - 如果有需要標籤, 就用innerHTML
>   - 如果沒有用標籤, 那就用 innerText(textContent), 
>     因為innerHTML有個解析過程, 效率比較低
```htmlmixed=
<body>
  <div id='box'>
    5566 不能亡
    <span> 那一年 默默無言 </div>
  </div>
  <script>
  var box = document.getElementById('box');
  
  console.dir(box);
    // innerHTML: "↵			5566 不能亡 ↵			<span> 那一年 默默無言 </span>"
    // innerText: "5566 不能亡 那一年 默默無言"
    // textContent: "↵			5566 不能亡 ↵			 那一年 默默無言 "

  // box.innerHTML = "<a href='https://www.google.com'>Google</a>";
    // 寫是一個Google連結
  box.innerText = "<a href='https://www.google.com'>Google</a>";
    // 顯示這個str完整內容
  </script>
</body>
```
> #### innerText 與 textContent 兼容問題處理
> - 兩個都支持: Google, 新版Firefox, 新版IE(IE9+)
> - 只支持 innerText: 舊版IE(IE8-)
> - 只支持 textContent: 舊版Firefox

```htmlmixed=
<body>
  <div id='box'>
    5566 不能亡 
    <span> 那一年 默默無言 </div>
  </div>
  <script>
  var box = document.getElementById('box');
  console.log(getInnerText(box));	


  // 運用:
    // 屬性不存在時, 返回undefined
  console.log(typeof box.a); // undefined
    // 屬性存在時, 返回屬性類型
  console.log(typeof box.id); // string

  function getInnerText(element) {
    if (typeof element.innerText == 'string') { // 返回string 表示有支持, 那就返回
      return element.innerText
    } else {
      return element.textContent
    }
  }
  </script>
</body>
```

### 表單元素屬性
> - 不只一個值: value, type, 
> - 只有一個值: disabled(禁用), checked(默認控件), selected(下拉選單)
>   - 如果只有一個值, DOM 對應的元素屬性值為 Boolean
```htmlmixed=
<body>
  <input type='button' id='butn' value='封印'/>
  <input type='text' id='txt'/>
  <input type='button' id='butn5566' value='5566'/>
  <script>
  var butn = document.getElementById('butn');

  var flag = 1;
  butn.onclick = function () {
    var txt = document.getElementById('txt');
    console.dir(txt.disabled)
    if (flag === 1) {

      // txt.disabled = 'disabled'; 這也能用, 只是DOM既然是Boolean, 那就用Boolaen
      txt.disabled = true;
      butn.value = '封印解除'
      flag = 2;
    } else if (flag === 2) {
      flag = 1;
      // txt.disabled = '';
      txt.disabled = false;
      butn.value = '封印'
    }
  }

  var butn5566 = document.getElementById('butn5566');
  butn5566.onclick = function () {
    txt.value = '5566不能亡';
  }
  </script>
</body>
```

> #### 練習
> - 1. 解析完頁面後就依序添加數字到各個文本框中
> - 2. 輸出所有文本框中的值, 以 ` | ` 隔開

```htmlmixed=
<body>
  <input type='text' /><br />
  <input type='text' /><br />
  <input type='text' /><br />
  <input type='text' /><br />
  <input type='text' /><br />
  <input type='text' /><br />
  <input type='text' /><br />
  <input type='text' /><br />
  <input type='text' /><br />
  <input type='button' id='butn' />

  <script>
  var texts = document.getElementsByTagName('input');
  for (i=0; i<texts.length; i++) {
    if (texts[i].type === 'text') { // 因為有個input是button, 要篩掉
      texts[i].value = i
    }
  }

  var butn = document.getElementById('butn');
  butn.onclick = function () {

    /* 用拼接的如果太大量, 會有效能問題
    var outPut = '';
    for (i=0; i<texts.length; i++) {
      if (texts[i].type === 'text') {
        outPut += texts[i].value + ' | ';
      }
    }
    console.log(outPut);
    outPut = outPut.substr(0, outPut.length-3);
    console.log(outPut);
    */
    
    // 用陣列快很多, 因為他是直接在後面添加, 不用重新開空間
    var arr = [];
    for (i=0; i<texts.length; i++) {
      if (texts[i].type === 'text') {
        arr.push(texts[i].value);
      }
    }
    console.log(arr);
    str = arr.join(' | ');
    console.log(str);
  }
  </script>
</body>
```

> #### 練習: 檢測登入
> - 帳號限制3-6
> - 密碼限制6-8
> - 按登入後檢測有問題, 紅框

```htmlmixed=
<body>
  <input type='text' id='userName'/><br />
  <input type='password' id='userPassword'/><br />
  <input type='button' id='butn' value='login'/>

  <script>
  
  // 按鈕後才檢測, 所以找到按鈕
  var butn = document.getElementById('butn');
  butn.onclick = function () {
    // 要檢測的東西
    var userName = document.getElementById('userName');
    var userPassword = document.getElementById('userPassword');

    // 檢測規則
    if (userName.value.length <3 || userName.value.length >6) {
      userName.style = 'background: red;';
      return // 改背景顏色沒用, 要終止程序才有用
    } else {
      userName.style = '';
    }
    if (userPassword.value.length <6 || userPassword.value.length >8) {
      userPassword.style = 'background: red;';
      return
    } else {
      userPassword.style = '';
    }
    
    // 檢測成功
    console.log('登入');
  }
  </script>
</body>
```

> #### 練習: 顯示密碼
> - 點圖示會顯示密碼, 再點一次會關閉顯示密碼
>   - 作法: 點擊圖片, 切換 input.type  ( password <-> text )
```htmlmixed=
<head>
  <meta charset='utf-8'>
  <style>
  .box {
    border: 1px solid red;
    width: 330px;
    margin: 200px auto;
    position: relative;
  }
  .box input {
    width: 300px;
    height: 30px;
    outline: none;	
  }
  .box label {
    width: 15px;
    position: absolute;
    right: 46px;
    top: 3px;
  }
  </style>
</head>
<body>
  <div class='box'>
    <label id='photo'><img src='https://api.fnkr.net/testimg/30x30/F00'/></label>	
    <input type='password' id='open'/>
  </div>

  <script>
  var photo = document.getElementById('photo');
  var flag = 1;
  photo.onclick = function () {
    var open = document.getElementById('open');
    if (flag === 1) {
      flag = 2;
      open.type = 'text';
    } else {
      flag = 1;
      open.type = 'password';
    }
  }

  </script>
</body>
```

> #### 練習: 隨機點餐(seleected)
> - 按下按鈕, 隨機選擇吃什麼
```htmlmixed=
<body>
  <input type='button' id='btnOption'/><br />
  <select id='eatWhat'>
    <option>薯條</option>
    <option>漢堡</option>
    <option>雞塊</option>
  </select>


  <script>
  // 獲取按鈕
  var btnOption = document.getElementById('btnOption');
  btnOption.onclick = function () {
    // 獲取選項
    var eatWhat = document.getElementById('eatWhat');
    var eatWhats = eatWhat.getElementsByTagName('option');
    // console.log(eatWhats);

    // 獲取隨機數字
    var randomOption = Math.floor(Math.random() * eatWhats.length);
    // console.log(randomOption);

    // 改變選擇
    // console.log(eatWhats[randomOption]);
    eatWhats[randomOption].selected = true;
  }
  </script>
</body>
```

> #### 練習: 請輸入關鍵字
> - `onfocus` : 獲得焦點事件
> - `onblur` : 失去焦點事件

```htmlmixed=
<body>
  <input id='txt' type='text' style='color: gray;' value='請輸入'/>
  <script>
  var txt = document.getElementById('txt');

  // onfocus: 獲得焦點事件
  txt.onfocus = function () {
    // console.log('hah');
    if (this.value === '請輸入') {
      this.value = '';
      this.style = 'color: black;'
    }
  }
  // onblur: 失去焦點事件
  txt.onblur = function () {
    if (this.value.length === 0 || this.value === '請輸入') { // 避免使用者輸入'請輸入', 所以加那個 || 條件
      this.value = '請輸入';
      this.style = 'color: gray;'
    }
  }
  </script>
</body>
```
> #### 練習: 全選與反選
> - 按全選紐時, 選項全部勾起來或全部不勾
> - 個別按鈕如果全部都選中了, 那全選鈕就要勾起來, 有缺就不勾
> - 反選鈕就反選個別扭的選項, 如果反選鈕把全部都勾起來, 那全選就要勾, 反之就不勾

> - ! 這有點難, 以後有空回來多做幾次

```htmlmixed=

<body>
  <input type='checkbox' id='checkAll'>
  <div id='checkEach'>
    <input type='checkbox'/>
    <input type='checkbox'/>
    <input type='checkbox'/>
  </div>
  <input type='button' id='btn' value='反選'/>

  <script>
  // 取得按鈕們的控制
  var checkAll = document.getElementById('checkAll');
  var checkEach = document.getElementById('checkEach');
  var inputs = checkEach.getElementsByTagName('input');

  // 1. 把選項們的按鈕來出來跟全選紐連結,
  //    按全選時, 子按鈕要全部勾起來,
  //    再按一次, 子按鈕就不勾
  checkAll.onclick = function () {
    // 1-1 先取得子按鈕們
    for (i=0; i<inputs.length; i++) {
      var input = inputs[i]
      // 為了避免盒子裡面的inputs不只checkbox, 所以要特別篩選
      if (input.type === 'checkbox') { 
        // 1-2 連結全選與子按鈕
        input.checked = this.checked;
      }
    }
  }

  // 2. 按個別扭時要判斷有沒有全選, 如果有, 
  //    那全選紐要勾起來, 反之如果有缺, 那全選鈕就不能勾
  // 2-1 註冊按鈕
  for (i=0; i<inputs.length; i++) {
    var input = inputs[i];
    if (input.type === 'checkbox') {
      input.onclick = function () {
        // 2-2 按按鈕時, 判斷有沒有人沒有勾選
        var isCheckAll = true; // 假設大家都有勾
        for (i=0; i<inputs.length; i++) {
          var input = inputs[i];
          if (input.type === 'checkbox') {
            // 如果有沒沒勾, 就改標記為 false
            if (input.checked !== true) {
              isCheckAll = false;
            }
          }
        }
        // 2-3 跑完2-2就知道有沒有人沒有勾, 那全選鈕就跟著標記就行了
        checkAll.checked = isCheckAll;
      }
    }
  }

  // 3. 反選鈕, 把所有個別選項取反, 
  //    如果把全部都勾起來時, 全選紐要勾起來, 否則就不勾
  // 3-1 取得按鈕控制
  var btn = document.getElementById('btn');
  btn.onclick = function () {
    var isCheckAll = true;
    for (i=0; i<inputs.length; i++) {
      var input = inputs[i];
      if (input.type === 'checkbox') {
        // 3-2 對子按鈕反選
        input.checked = !input.checked;
        // 3-3 確認子按鈕按鈕有沒有全選
        if (input.checked !== true) {
          isCheckAll = false;	// 沒有就標記沒有
        }
      }
    }
    // 3-4 全選鈕連結標記
    checkAll.checked = isCheckAll;
  }
  </script>
</body>
```

> #### `select()`
> - 讓 Text Box 裡面的文字獲得選取狀態

```htmlmixed=
<body>
  <input type='text' value='tmp'/>
  <script src='test.js'></script>
</body>
```
```javascript=
var inp = document.querySelector('input');

// TextBox 獲得焦點時
inp.onfocus = function () {
  inp.select(); // 文字選取
}
```


### 樣式屬性操作
### style
`element.style.attr = 'value';`
> - JS 的樣式採用駝峰命名, 例如 backgroundColor
> - JS 修改 style 始於行內樣式 ( 權重較高 )
> - 樣式改的比較少時, 使用 style
```htmlmixed=
<head>
  <meta charset='utf-8'>
  <style>
  .box {
    border: 1px solid red;
    width: 330px;
    height: 200px;
    margin: 200px auto;
  }
  </style>
</head>
<body>
  <div class='box'></div>

  <script>
  var div = document.querySelector('div');
  div.onclick = function () {
    this.style.backgroundColor = 'blue';
    this.style.width = '400px';
  }
  </script>
</body>
```

> #### 練習: 點擊小圖後隱藏整個框
> - 常用於一些附有 x 圖示的廣告

```htmlmixed=
<head>
  <meta charset='utf-8'>
  <style>
  .box {
    margin: 100px auto;
    width: 300px;
  }
  </style>
</head>
<body>
  <div class='box'>
    <img src='https://api.fnkr.net/testimg/30x30/'/>
    <img src='https://api.fnkr.net/testimg/100x100/'/>
  </div>

  <script>
  var div = document.querySelector('div');
  var imgs = div.getElementsByTagName('img');

  imgs[0].onclick = function () {
    div.style.display = 'none'; // 點擊小框後把整個 div 隱藏
  }
  </script>
</body>
```

> #### 練習: 精靈圖循環
> - 使用前提: sprite 擺放的位置要有規律
> - 使用效果: 這樣就不用每個 li 個別改位置了, 非常屌的小技巧

```htmlmixed=
<head>
  <meta charset='utf-8'>
  <style>
  * {
    margin: 0;
    padding: 0;
  }

  div {
    width: 150px;
    margin: 200px auto;
  }
  ul {
    list-style: none;
  }
  li {
    height: 20px;
    width: 30px;
    /* background: url(sprite.png) no-repeat*/
    /* 背景應該要放精靈圖路徑, 因為打成網路筆記, 所以我沒寫*/
    background-color: gray; 
    margin: 20px;
    float: left;
  }
  </style>
</head>
<body>
  <div class='box'>
    <ul>
      <li></li>
      <li></li>
      <li></li>
      <li></li>
      <li></li>
      <li></li>
    </ul>
  </div>

  <script>
  var lis = document.getElementsByTagName('li');
  for (i=0; i<lis.length; i++) {
    // 根據sprite圖的位置去計算, 
    // 例如每張圖間隔y 10px, -> i * 10px
    var index = i * 10; 
    
    // 每次循環拼接成出要使用的位置
    // 0 0, 0 10, 0 20, 0 30,
    lis[i].style.backgroundPosition = '0 -' + index + 'px';
  }
  </script>
</body>
```


> #### 練習: 開關燈

```htmlmixed=
<body>
  <input type='button' value='開關燈'/>

  <script>
  var input = document.querySelector('input');
  var isLight = true;
  input.onclick = function () {
    var body = document.querySelector('body');
    if (isLight === true) {
      body.style.backgroundColor = 'black';
      isLight = false;
    } else {
      isLight = true;
      body.style.backgroundColor = 'white';
    }
  }
  </script>
</body>
```

### 練習: 鼠標經過表格欄位變色
> - `onmouseover` : 鼠標經過
> - `onmouseout` : 鼠標離開
```htmlmixed=
<head>
  <meta charset='utf-8'>
  <style>
  table {
    margin: 100px auto;
    width: 600px;
  }
  thead {
    background-color: skyblue;
  }
  </style>
</head>
<body>
  <table>
    <thead>
      <tr>
        <td>tead1</td>
        <td>tead2</td>
        <td>tead3</td>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>tbody1</td>
        <td>tbody2</td>
        <td>tbody3</td>
      </tr>
      <tr>
        <td>tbody4</td>
        <td>tbody5</td>
        <td>tbody6</td>
      </tr>
      <tr>
        <td>tbody7</td>
        <td>tbody8</td>
        <td>tbody9</td>
      </tr>
    </tbody>
  </table>
  
  <script>
  // 取得 tbody 裡面的 tr
  var tr = document.querySelector('tbody').querySelectorAll('tr');
  // console.log(tr);
  // 註冊
  for (i=0; i<tr.length; i++) {
    // 鼠標經過
    tr[i].onmouseover = function () {
      this.style.backgroundColor = 'darkblue';	
    }
    // 鼠標離開
    tr[i].onmouseout = function () {
      this.style.backgroundColor = '';	
    }
  }
  </script>
</body>
```





### className
> - 如果改比較複雜的樣式時, 可以先把css寫好, 然後觸發時添加 class
> - class 是保留字, DOM對應的 attr 是 className
> - 如果原本的標籤已經有類名, 記得觸發時要寫上該類名
>   - 例如`<div class='test'>` -> `div.className = 'test test2'`
```htmlmixed=
<head>
  <meta charset='utf-8'>
  <style>
  * {
    margin: 0;
    padding: 0;
  }
  div {
    width: 300px;
    margin: 100px auto;
    font-size: 10px;
  }
  div p {
    display: inline-block;
  }
  div input {
    vertical-align: middle;
    line-height: 100%;
  }

  /* 先寫好觸發樣式 */
  .wrong {
    border: 1px solid red;
  }
  .right {
    border: 1px solid green;
  }
  </style>
</head>
<body>
  <div>
    <input type='password' id='psd'/>
    <p id='pwdFont'>請輸入6-10位密碼</p>
  </div>

  <script>
  var psd = document.getElementById('psd');
  psd.onblur = function () {
    var pwdFont = document.getElementById('pwdFont'); 
    if (this.value.length < 6 || this.value.length > 10) {
      this.className = 'wrong';	 // 觸發時添加類
      pwdFont.style.color = 'red'; // 比較一下
      pwdFont.innerText = '請輸入6-10位密碼';

    } else {
      this.className = 'right'; // 觸發時添加類
      pwdFont.style.color = 'green';
      pwdFont.innerText = '輸入正確';
    }
  }
  </script>
</body>
```

### 練習: 點擊換背景色
> - 點擊顏色, 換背景顏色

```htmlmixed=
<head>
  <meta charset='utf-8'>
  <style>
  .father {
    width: 300px;
    height: 300px;
    display: flex;
    margin: 100px auto;
    border: 1px solid red;
  }
  .red {
    background-color: red;
    flex: 1;
  }
  .blue {
    background-color: blue;
    flex: 1;
  }
  .green {
    background-color: green;
    flex: 1;
  }
  </style>
</head>
<body>
  <nav class='father'>
    <div class='red'></div>
    <div class='blue'></div>
    <div class='green'></div>
  </nav>
  <script>
  var divs = document.getElementsByTagName('div');
  for (i=0; i<divs.length; i++) {
    divs[i].onclick = function () {
      // console.log(this.className);	
      var body = document.querySelector('body');
      body.className = this.className;
    }
  }

  </script>
</body>
```





### 自定義屬性操作
> - 有些數據可以設置到頁面而不用保存到數據庫

> - `element.getAttribute('attr')` : 取得屬性
>   - vs. 獲取內置屬性 `element.property`
> - `element.setAttribute('attr', 'value')` : 設置屬性
>   - vs. 設置內置屬性 `element.property = 'value'`
> - `element.removeAttribute('attr')` : 刪除屬性


```htmlmixed=
<head>
  <meta charset='utf-8'>
  <style>
  .grayBorder {
    border: 1px solid gray;	
  }
  </style>
</head>
<body>
  <div id='box' apple='15' banana='10'>Test</div>

  <script>
  // 取得屬性
  var box = document.getElementById('box');
  console.log(box.id); // box
    // 直接取是取不到的
  console.log(box.apple); // undefined
  console.log(box.banana); // undefined
    // 自定義屬性存在 getAttribute 裡
  console.log(box.getAttribute('apple')); // 15
  console.log(box.getAttribute('banana')); // 10

  // 新增屬性
  box.setAttribute('cat', '100');
  console.log(box.getAttribute('cat')); // 100
  box.setAttribute('class', 'grayBorder'); // 這裡是 class 不是 className
  
  // 修改屬性
  box.setAttribute('apple', '150');
	console.log(box.getAttribute('apple')); // 150

  // 刪除屬性
  box.removeAttribute('apple');
  console.log(box.getAttribute('apple')); // null
  </script>

</body>
```

### 排他思想
> - 先清除所有, 再設置當前

```htmlmixed=
<body>
  <input type='button'/>
  <input type='button'/>
  <input type='button'/>
  <input type='button'/>

  <script>
  var btns = document.getElementsByTagName('input');
  for (i=0; i<btns.length; i++) {
    btns[i].onclick = function () {
      // 按下按鍵後要設值顏色之前, 要先把所有人的顏色清空, 否則上次onclick的狀態還會在
      for (i=0; i<btns.length; i++) {
        btns[i].style.backgroundColor = '';
      }
      this.style.backgroundColor = 'red';
    }
  }

  </script>
</body>
```


### 點擊切換欄位
> - 最難的點: 如何取得被點擊者的位置
>   - 解: `this.setAttribute` 賦予位置, 用 `this.getAttribute` 取得

```htmlmixed=
<head>
  <meta charset='utf-8'>
  <style>
  /* 初始化 */
  * {
    margin: 0;
    padding: 0;
  }
  ul {
    list-style: none;
  }
  .clearfl:before,
  .clearfl:after {
    content: '';
    display: table;
  }
  .clearfl:after {
    clear: both;
  }

  /* 盒子基本設置 */
  .box {
    width: 600px;
    margin: 100px auto;
  }
  .title {
    background: skyblue;
  }
  li {
    float: left;
    width: 200px;
  }
  
  /* 盒子變化設置 */
  .cur {
    background: red;
  }
  .main div {
    display: none;
  }
  </style>
</head>
<body>
  <div class='box'>
    <div class='title clearfl'>
      <ul>
        <li class='cur'>li1</li>
        <li>li2</li>
        <li>li3</li>
      </ul>
    </div>
    <div class='main'>
      <div style='display: block;'>choice1</div>
      <div>choice2</div>
      <div>choice3</div>
    </div>
  </div>


  <script>
  // 拿到 tab 欄位控制
  var lis = document.querySelector('.title').querySelectorAll('li');
  for (i=0; i<lis.length; i++) {

    // 最重要的一步, 自定義位置
    // 必須知道我點了哪個 tab, 所以為每個 tab 標註位置
    lis[i].setAttribute('index', i); 
    
    lis[i].onclick = function () {
      var divs = document.querySelector('.main').querySelectorAll('div');
      // 清除樣式 ( 排他 )
      for (i=0; i<lis.length; i++) {
        lis[i].className = '';
        divs[i].style = '';
      }
      // 賦予被點擊者樣式
      this.className = 'cur';
      // 拿到被點擊者位置
      var index = this.getAttribute('index');
      // 賦予對應的盒子顯示
      divs[index].style.display = 'block';
    }
  }
  </script>
</body>
```

### H5 自定義屬性
> - H5 規定自定義屬性以 `data-` 開頭, 以區別內置屬性
>   - 例如 `elem.setAttribute('data-index', 0);`
> - H5 新增 dataset 的 proprety 來獲取 data- 開頭的自定義屬性
> - 如果自定義屬性有多個-連接, dataset 採駝峰命名

> - dataset 在 IE 11以上才支援

```htmlmixed=
<body>
  <div index='0'></div>

  <script>
  var div = document.querySelector('div');

  // 問題: 難以直接看出他是內置還是自定義
  console.log(div.index); // undefined
  console.log(div.getAttribute('index')); // 0 

  // H5 規定 data- 開頭
  div.setAttribute('data-index', 0);
  console.log(div.getAttribute('data-index')); // 0

  // H5 新增的獲取自定義屬性 dataset
  // dataset 就是 data- 開頭的自定義屬性集合
  console.log(div.dataset); // DOMStringMap {index: "0"}
  console.log(div.dataset.index); // 0
  console.log(div.dataset['index']); // 0

  // 如果自定義屬性有多個-連接, dateset獲取時採駝峰命名
  div.setAttribute('data-list-name', 'Racc');
  console.log(div.dataset.listName); // Racc
  </script>
</body>
```

## 節點

### 模擬 DOM 結構
> - 觀察 DOM 結構
>   - 沒有任何 `undefined`, 要馬有東西, 要馬`null`, `''`, `[]`, `{}`
```htmlmixed=
<body>
  <div id='tmp'>
    123
    <span id='tmp2'>456</span>
  </div>
  <script>
  var tmp = document.getElementById('tmp');
  var tmp2 = document.getElementById('tmp2');
  console.dir(tmp);
  console.dir(tmp2);
  </script>
</body>
```

> - 測試 js 如果找不到屬性時, 會如何處理

```javascript=
function Node(param) {
  this.className = param.className;
}

// 如果參數對象有該屬性, 就會顯示該屬性
  var a = new Node({
    className: 'haha',
  })
  console.dir(a);
  // className: "haha"
  // __proto__:
  //   constructor: ƒ Node(param)
  //   __proto__: Object

// 如果參數對象沒有該屬性, 則會顯示 『 undefined 』
  var b = new Node({})
  console.dir(b);
  // className: undefined
  // __proto__:
  //   constructor: ƒ Node(param)
  //   __proto__: Object
```
> - 所以構造函數必須要做一些判斷處理
```javascript=
function Node(param) {
  /* 太囉唆
  if (param.className) {
    this.className = param.className;
  } else {
    this.className = '';
  }
  */
  
  // 三元運算還是太長
  // this.className = param.className ? param.className : '' ;
  
  // 讚讚
  this.className = param.className || '';
    // 如果有, 返回 <value>
    // 如果沒, 返回 ''
}

var a = new Node({
  className: 'haha',
})
console.dir(a); // className: "haha"

var b = new Node({})
console.dir(b); // className: ""
```

> - DOM 節點的一些重要基本屬性
```htmlmixed=
<body>
  <div>
    123
    <!-- 456 -->
  </div>
</body>
```
```javascript=
console.dir(div)
// nodeName: "DIV"
// nodeType: 1
// nodeValue: null

// childNodes: NodeList(1)
//   0: text
//     nodeName: "#text"
//     nodeType: 3
//     nodeValue: "↵			123↵			"
//   1: comment
//     nodeName: "#comment"
//     nodeType: 8
//     nodeValue: "456"
//   2: text
//   length: 3
//   __proto__: NodeList

console.dir(tmp.attributes[0]);
// nodeName: "id"
// nodeType: 2
// nodeValue: "tmp"
// childNodes: NodeList(0)
//   length: 0
//   __proto__: NodeList

```
```javascript=
// 以設置元素節點為例, 否則太複雜了要寫很久== 

function Node(param) {
  // 設置 attr.default.value
  
  // nodeName 元素節點默認為標籤名
  this.nodeName = param.nodeName || '';
  // nodeType 元素節點=1; 屬性節點=2; 文本節點=3
  this.nodeType = param.nodeTyle || 1;
  // nodeValue 元素節點的值為 null
  this.nodeValue param.nodeValue || null;
  // 子節點
  this.childNodes = param.childNodes || [];
}

var html = new Node({
  nodeName: 'html',
})
var body = new Node({
  nodeName: 'body',
})
var head = new Node({
  nodeName: 'head',
})
html.childNodes.push(body);
html.childNodes.push(head);
console.log(html);
  // childNodes: (2) [Node, Node]
  // nodeName: "html"
  // nodeType: 1
  // nodeValue: null
```

> - 節點至少擁有 nodeType, nodeName, nodeValue 三個基本屬性
>   - 元素節點: nodeType = 1
>   - 屬性節點: nodeType = 2
>   - 文本節點(文字, 空格, 換行..等): nodeType = 3
>   - 註釋節點(comment): nodeType = 8
> - 瀏覽器內部維護一顆DOM樹

### 父節點
`node.parentNode`
> - parentNode 可以返回某節點的父節點
> - 當指定節點沒有父節點時, 返回 null (#document 的父節點)


```htmlmixed=
<body>
  <div id='grandFa'>
    <div id='fa'>
      <div id='son'></div>
    </div>
  </div>
  
  <script>
  var son = document.getElementById('son');
  var fa = document.getElementById('fa');

  console.log(fa); // <div id='fa'>...</div>
  console.log(son.parentNode); // <div id='fa'>...</div>

  console.log(son.parentNode.parentNode);	// <div id='grandFa'></div>
  console.log(son.parentNode.parentNode.parentNode); // <body></body>	
  console.log(son.parentNode.parentNode.parentNode.parentNode); // <html lang='en'></html>
  console.log(son.parentNode.parentNode.parentNode.parentNode.parentNode); // #document
  console.log(son.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode); // null	
  </script>
</body>
```

### 子節點
`node.childNodes`
> - 返回所有子節點集合, 包含元素節點, 文本節點等
>   - 空行也是節點( #text )
> - 但經常情況下都在處理元素節點
>   - 解決辦法1 : 利用 nodeType 來找出元素節點
>   - 解決辦法2 : `node.children` (非標準)
>     - 雖然是非標準, 但是所有瀏覽器都有支援

> - `hasChildNodes()` : 判斷是否有子節點

```htmlmixed=
<body>
  <ul id='testUl'>
    <li>test</li>
    <li>test</li>
    <li>test</li>
  </ul>

  <script>
  var ul = document.getElementById('testUl');
  var lis = ul.getElementsByTagName('li');

  console.log(lis); // HTMLCollection(3) [li, li, li]

  // childNodes 返回了所有的子節點集合, 包含元素節點, 文本節點等
  console.log(ul.childNodes); // NodeList(7) [text, li, text, li, text, li, text]
  console.dir(ul);
  // innerHTML: "↵			<li>test</li>↵			<li>test</li>↵			<li>test</li>↵		"

  // 解決辦法1. 利用 nodeType 來找到元素節點
  console.log(ul.childNodes[0].nodeType); // 3
  console.log(ul.childNodes[1].nodeType); // 1

  var ulChildNode = [];
  for (i=0; i<lis.length; i++) {
    if (lis[i].nodeType === 1) {
      ulChildNode.push(lis[i]);
    }
  }
  console.log(ulChildNode); // (3) [li, li, li]


  // 解決辦法2. node.children
  console.log(ul.children); // HTMLCollection(3) [li, li, li]
  </script>
</body>
```
> - 簡單案例
```htmlmixed=
<body>
  <div id='tmp'>
    <span>1</span>
    <span>2</span>
    <span>3</span>
    <span>4</span>
  </div>
  <script>
  var tmp = document.getElementById('tmp');
  // 判斷有沒子節點
  if (tmp.hasChildNodes()) {
    for (var i = 0; i < tmp.children.length; i++) {
      if (i % 2 == 0) {
        tmp.children[i].style.backgroundColor = 'yellow';
      } else {
        tmp.children[i].style.backgroundColor = 'orange';
      }
    }
  }
  </script>
</body>
```


### 首節點與末節點
> #### 子節點
> - `node.firstChild` : 所有子節點的第一個(包含非元素節點)
> - `node.lastChild` : 所有子節點的最後一個(包含非元素節點)
> - 找無, 返回 `null`

> #### 元素子節點
> - `node.firstElementChild` : 第一個元素子節點 
> - `node.firstElementChild` : 最後一個元素子節點
> - 這個有兼容性問題, IE9以上才支援

> #### children
> - `ul.children[0]` : 第一個元素子節點 
> - `ul.children[ul.children.length - 1]` : 最後一個元素子節點


```htmlmixed=
<body>
  <ul id='testUl'>
    <li>test1</li>
    <li>test2</li>
    <li>test3</li>
  </ul>

  <script>
  var ul = document.getElementById('testUl');
  console.log(ul.firstChild); // #text
  console.log(ul.lastChild); // #text

  console.log(ul.firstElementChild); // <li>test1<li>
  console.log(ul.lastElementChild); // <li>test3<li>


  console.log(ul.children[0]);
  console.log(ul.children[ul.children.length - 1]);
  </script>
</body>
```
> - 處理兼容
```javascript=
<body>
  <div id='tmp'>
    <!--
    <span>1</span>
    <span>2</span>
    <span>3</span>
    <span>4</span>
    -->
  </div>
  <script>
  var tmp = document.getElementById('tmp');
  function getFirstElementChild(elem) {
    var node, nodes = elem.childNodes, i = 0;
    while (node = nodes[i++]) {
      if (node.nodeType === 1) {
        return node
      }
    }
    return null
  }

  console.log(getFirstElementChild(tmp));
  </script>
</body>
```

> - 綜合小練習: 被點擊的元素`<a>`改變背景色

> - 讓 a 不跳轉的方法 href 的方法
>   - `<a href='javascript:void(0);'>` 或 
>     `<a href='javascript:;'>` 或 
>     `<a href='javascript:undefined;'>`
>     - 點擊`javascript:` 時, 會執行 URI 的 code, 並返回結果來代替內容, 
>       除非結果是 `undefined`
>     - void 是一種運算符, 返回 undefined
>   - 惟每次點擊都要運算而得到 `undefined` 後不執行, MDN 不推薦此種做法
>   - MDN 推薦直接綁定 onclick 來 return false, 點擊不執行
```htmlmixed=
<body>
  <div id='tmp'>
    <ul>
      <li><a href='javascript:;'>1</a></li>
      <li><a href='javascript:;'>2</a></li>
      <li><a href='javascript:;'>3</a></li>
    </ul>
  </div>
  
  <script>
  // 先拿到最外層父元素
  var tmp = document.getElementById('tmp');
  // 兼容函數
  function getFirstElementChild(elem) {
    var node, nodes = elem.childNodes, i = 0;
    while (node = nodes[i++]) {
      if (node.nodeType === 1) {
        return node
      }
    }
    return null
  }

  // 拿到 ul
  var tmpUl = getFirstElementChild(tmp);

  // 拿到 ul 裡面的東西
  for (var i = 0; i < tmpUl.children.length; i++) {
    var li = tmpUl.children[i], 
        a = getFirstElementChild(li);
    
    // 點擊時調用 aClick
      // 不建議在循環內定義 function, 浪費內存, 拉到外面寫後再指向就好了
      // 切記不是 aClick(), 這會返回一個返還值
      // a.onclick 想要綁定的不是一個值, 是一個函數
    a.onclick = aClick;
  }

  function aClick() {
    // 排他
    for (var i = 0; i < tmpUl.children.length; i++) {
      // 拿到ul裡面所有li
      var li = tmpUl.children[i];
      // 清掉背景色
      li.style.backgroundColor = '';
    }
    // 為被點擊的<a> 的父節點(li)上背景色
    this.parentNode.style.backgroundColor = 'red';
    // 被點擊的<a>不執行任何動作(MDN推薦)
    return false
  }

  </script>
</body>
```

### 兄弟節點

> #### 子節點
> - `node.nextSibling` : 下一個節點
> - `node.previousSibling` : 上一個節點

> #### 子元素節點
> - `nextElementSibling` : 下一個兄弟元素節點
> - `previousElementSibling` : 上一個兄弟元素節點
> - 有兼容問題, IE9+

> - 如果沒有就返回 null
```htmlmixed=
<head>
  <meta charset='utf-8'>
  <style>
  </style>
</head>
<body>
  <div></div>
  <nav></nav>

  <script>
  var div = document.querySelector('div');

  // 兄弟節點
  console.log(div.nextSibling); // #text
  console.log(div.previousSibling); // #text

  // 兄弟元素節點(IE9+)
  console.log(div.nextElementSibling); // <nav></nav>
  console.log(div.previousElementSibling); // null

  // 兼容函數
  function getElementSibling(elem) {
    // 一直往下找, 找到 null 的 Boolean 就是 false 而會停止
    while (elem = elem.nextSibling) { 
      if (elem.nodeType === 1) { // 找到元素就返回
        return elem;
      }
    }
    return null // 全部找完都沒就返回 null
  }
  // 兼容函數
  function getPreviousElementSibling(elem) {
    while (elem = elem.previousSibling) {
      if (elem.nodeType === 1) {
        return elem;
      }
    }
    return null
  }
  console.log(getElementSibling(div)); // <nav></nav>
  console.log(getPreviousElementSibling(div)); // null
  </script>
</body>
```

### 創建節點與添加節點

> #### 創建節點 
> `document.createElement('tagName')`
> - 動態創建元素節點

> #### 添加節點
> - `node.appendChild(child)`
>   - 添加到指定父元素的子元素列表最後
>   - 類似 css 的 append 的 after偽元素
> - `node.insertBefore(child, 指定元素)`;
>   - 添加到指定父元素的指定子元素前面


```htmlmixed=
<body>
  <ul>
    <li>123</li>
  </ul>

  <script>
  var ul = document.querySelector('ul');

  // 創建節點
  var li1 = document.createElement('li');
  // append 添加
  ul.appendChild(li1);

  var li2 = document.createElement('li');
  // insert 添加
  ul.insertBefore(li2, ul.children[0]);
  </script>
</body>
```

> #### appendChild 補充
> - 被插入的節點如果已經在文檔中, 則會先從該位置移除後插入指定位置
```htmlmixed=
<head>
  <meta charset='utf-8'>
  <style>
  .one {
    height: 100px;
    background: skyblue;
  }
  .two {
    height: 100px;
    background: darkblue;
    color: white;
  }
  </style>
</head>
<body>

  <div class='one'>
    <span>123</span>
    <button>magic</button>
  </div>
  
  <div class='two'></div>
  
  <script>
  var btn = document.querySelector('button');
  btn.addEventListener('click', function () {
    var one = document.querySelector('.one');
    var two = document.querySelector('.two');
    two.appendChild(one.children[0]);
  })
  </script>
</body>
```

### 練習: 選中跳選單
<img src='https://i.imgur.com/aVxjuXW.png' style='width: 200px;'/> 
<img src='https://i.imgur.com/u8AxBVL.png' style='width: 200px;'/>
<img src='https://i.imgur.com/hahdI36.png' style='width: 200px;'/>

> - `option.selected` 跟 `select.multiple` 是 Boolean 值
>   - `select.multiple` : 決定是否可以多選, 默認為 false (不可多選)
>   - `option.selected` : 有無選中, true 為選中

> - 還有一個BUG還沒解, 就是多次選中跳單後, 順序是亂的
```htmlmixed=
<head>
  <meta charset='utf-8'>
  <style>
  select {
    width: 100px;
    height: 200px;
    outline: none;
  } 
  </style>
</head>
<body>
  <select multiple='ture'>
    <option>1</option>
    <option>2</option>
    <option>3</option>
    <option>4</option>
    <option>5</option>
  </select>

  <button><<</button>
  <button>>></button>
  <button><</button>
  <button>></button>

  <select multiple='true'>
  </select>

  <script>
  var btns = document.querySelectorAll('button');
  var selects = document.querySelectorAll('select');

  btns[1].addEventListener('click', function() {

    /* i=0, 移走1, selects[0]=[2345], length=4;
     * i=1, 移走3, selects[0]=[245], length=3;
     * i=2, 移走5, selects[0]=[24]. length=2;
     * i=3, length=2 跳出
     * 所以只移走了135
    for (var i=0; i<selects[0].length; i++) {
      var option = selects[0].children[i];
      console.log(option);
      selects[1].appendChild(option);
    }
    */

    /* 每次都移最後一項
     * selects[0] = [12345]
     * i=4, 移走5, selects[0]=[1234], length-1=3;
     * i=3, 移走4, selects[0]=[123], length-1=2;
     * ...
     * 全部都移過去了, 可是順序反了
    for (var i=selects[0].length -1; i>=0; i--) {
      var option = selects[0].children[i];
      selects[1].appendChild(option);
    }
    */

    var len = selects[0].length;
    for (var i=0; i < len; i++) {
      var option = selects[0].children[0];
      selects[1].appendChild(option);
    }	
  })


  btns[0].addEventListener('click', function () {
    var len = selects[1].length;
    for (i=0; i<len; i++) {
      option = selects[1].children[0];
      selects[0].appendChild(option);
    }
  })

  btns[3].addEventListener('click', function() {
    var arr = [];
    for (var i = 0; i<selects[0].length; i++) {
      var option = selects[0].children[i]
      if (option.selected) {
        arr.push(option);
        option.selected = false;
      }
    }
    console.log(arr);
    for (i = 0; i<arr.length; i++) {
      console.log(i, arr);
      /* 我全部選中的輸出結果 
       * 這裡是操作 arr 不是操作標籤, 所以 length 不會減少
      0 (5) [option, option, option, option, option]
      1 (5) [option, option, option, option, option]
      2 (5) [option, option, option, option, option]
      3 (5) [option, option, option, option, option]
      4 (5) [option, option, option, option, option]
      */
      selects[1].appendChild(arr[i]);
    }
  })

  btns[2].addEventListener('click', function () {
    arr = [];
    for (i=0; i<selects[1].children.length; i++) {
      if (selects[1].children[i].selected) {
        arr.push(selects[1].children[i])
        selects[1].children[i].selected = false;
      }
    }
    console.log(arr);

    for (i = 0 ; i< arr.length; i++) {
      selects[0].appendChild(arr[i]);
    }
  })
  </script>
</body>
```


### 練習: 超陽春留言板
> - 點擊送出時, 
>   - 讀取textarea的內容,
>   - 創建元素, 
>   - 將內容附加到元素上
>   - 將元素丟到留言板父元素裡
```htmlmixed=
<body>
  <textarea></textarea>
  <input type='button' id='btn' value='送出'>
  <ul>
  </ul>

  <script>
  var btn = document.getElementById('btn');
  // 點擊送出
  btn.onclick = function () {
    var ul = document.querySelector('ul');
    var text = document.querySelector('textarea');

    // 確認有沒有打字
    if (text.value.length === 0) {
      alert('請打點字');
      return false
    } else {
      // 有打字就創建元素並賦予內容
      var li = document.createElement('li');
      li.innerHTML = text.value;
      
      // 想怎麼插入都行
      // ul.appendChild(li);
      ul.insertBefore(li, ul.children[0]);
      
      // 清空留言框
      text.value = '';	
    }
  }
  </script>
</body>
```

#### `element.insertAdjacentHTML(position, text);`

> - 將 str 解析成 HTML, 並將結果插入指定的DOM位置
>   - position:
>     - `'beforebegin'` : 指定元素的前面
>     - `'afterend'` : 指定元素的後面
>     - `'afterbegin'` : 指定元素的最初子節點的前面
>     - `'beforeend'` : 指定元素的最後子節點的後面
> - vs. `appendChild(node)`
>   - 不支持 str

> - ![](https://i.imgur.com/jBJAEeo.png)
  
```htmlmixed=
<body>
  <div>1</div>
  <script src='test.js'></script>
</body>
```
```javascript=
var div = document.querySelector('div');
var tmp = '<button>123</button>';

// 這只是要讓效果明顯的css而已
div.style.border = '1px solid blue';
div.style.width = '100px';

// 插入 tmp 到 div 裡面的最後
div.insertAdjacentHTML('beforeend', tmp);
```





### 刪除節點
`node.removeChild(child)`
> - 刪除父節點裡指定的孩子
```htmlmixed=
<body>
  <input type='button' value='del'/>
  <ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
  </ul>

  <script>
  var btn = document.querySelector('input');
  var ul = document.querySelector('ul');

  // 每次按按鍵
  btn.onclick = function () {
    if (ul.children.length === 0) {
      return false
    } else {
      // 刪除ul裡第一個li
      ul.removeChild(ul.children[0]);
    }
  }
  </script>
</body>
```

> #### `node.remove();`
> - 從 DOM 刪除元素

```htmlmixed=
<body>
  <div>1</div>
  <div>2</div>
  <div>3</div>
  <script src='test.js'></script>
</body>
```
```javascript=
var divs = document.querySelectorAll('div');
for (var i = 0; i < divs.length; i++) {
  // 點擊後刪除該觸發元素
  divs[i].onclick = function () { 
    this.remove();
  }
}
```


### 練習: 刪除留言
> - 每個留言旁邊都設一個刪除鍵
> - 按下刪除鍵後刪除該留言

```htmlmixed=
<head>
  <meta charset='utf-8'>
  <style>
  li {
    width: 300px;
    background: skyblue;
    margin: 15px 0; /* 加大li間的大小, 否則a的高大於li的話會跑版 */
  }
  li a {
    float: right;
  }
  </style>
</head>
<body>
  <textarea></textarea>
  <input type='button' id='btn' value='留言'/>
  <ul></ul>

  <script>
  var btn = document.getElementById('btn');
  var text = document.querySelector('textarea');
  var ul = document.querySelector('ul');

  btn.onclick = function () {

    if (text.value.length === 0) {
      alert('請打點字');
      return false
    } else {
      var li = document.createElement('li');
      // 留言增加刪除鍵
      li.innerHTML = text.value + "<a href='javascript:;'>刪除</a>";
      // ul.appendChild(li);
      ul.insertBefore(li, ul.children[0]);
      text.value = '';	

      // 找到留言區裡所有刪除鍵
      var as = ul.querySelectorAll('a');
      for (i=0; i<as.length; i++) {
        // 按下刪除鍵後
        as[i].onclick = function () {
          // 刪除父元素(留言區)裡的子元素(留言),
          // 可是按的是 a , 所以子元素(li)是 this.parentNode
          ul.removeChild(this.parentNode)
        }
      }
    }
  }
  </script>
</body>
```




### 複製節點
`node.cloneNode()`
> - 淺拷貝: 
>   - 參數為空`cloneNode()` 或為false `cloneNode(false)`
>   - 只複製節點本身
> - 深拷貝: 
>   - 參數為true `cloneNode(true)` 
>   - 複製節點本身以及裡面所有的子節點

```htmlmixed=
<body>
  <input type='button' value='del'/>
  <ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
  </ul>

  <script>
  var btn = document.querySelector('input');
  var ul = document.querySelector('ul');

  btn.onclick = function () {
    var cloneLi = ul.children[0].cloneNode(true);
    ul.appendChild(cloneLi);
  }
  </script>
</body>
```

### 練習: 動態生成表格內容

```htmlmixed=
<head>
  <meta charset='utf-8'>
  <style>
  * {
    margin: 0;
    padding: 0;
  }
  table {
    margin: 100px auto;
  }
  th {
    width: 100px;
    background: skyblue;
  }
  tbody td {
    border: 1px solid red;
  }
  </style>
</head>
<body>
  <table>
    <!-- 表頭 -->
    <thead>
      <tr>
        <th>Data1</th>
        <th>Data2</th>
        <th>Data3</th>
        <th>刪除</th>
      </tr>
    <!-- 表身的資料動態創建 -->
    </thead>
    <tbody>
    </tbody>
  </table>


  <script>
  // 隨便寫幾個假資料
  var data = [
    {
      Data1: 'test1',
      Data2: 'test2',
      Data3: 'test3'
    }, {
      Data1: 'test4',
      Data2: 'test5',
      Data3: 'test6'
    }, {
      Data1: 'test7',
      Data2: 'test8',
      Data3: 'test9'
    }
  ]
  
  // 1. 根據資料個數創建tr (幾行)
  var tbody = document.querySelector('tbody');
  for (var i=0; i<data.length; i++) {
    var tr = document.createElement('tr');
    tbody.appendChild(tr);

    // 2. 根據資料項目個數創建 td (幾列)
    for (var key in data[i]) {
      var td = document.createElement('td');
      // 2-1 寫入資料 a = {b: 1}; -> a['b'] = 1
      td.innerHTML = data[i][key];
      tr.appendChild(td);
    }
    
    // 3. 根據資料個數創建刪除欄位
    var td = document.createElement('td');
    td.innerHTML = "<a href='javascript:;'>刪除</a>"
    tr.appendChild(td);

  }

  // 4. 完成刪除功能
  var as = tbody.querySelectorAll('a');
  for (var i=0; i<as.length; i++) {
    as[i].onclick = function () {
      // 是要刪除整行(tr)資料,
      // 表格結構是 tbody>tr>td>a, 
      // 所以要刪掉tr的話是 tbody.removeChild(tr)
      // 而 tr 是 a 的阿公
      tbody.removeChild(this.parentNode.parentNode)
    }
  }
  </script>
</body>
```

> - 191208 完全用JS寫一個

```javascript=
var item = ['Date1', 'Date2', 'Date3'];
var tmpDate = [
  {Date1: 'tmp1', Date2: 'tmp2', Date3: 'tmp3'},
  {Date1: 'tmp4', Date2: 'tmp5', Date3: 'tmp6'},
  {Date1: 'tmp7', Date2: 'tmp8', Date3: 'tmp9'},
]

// <body> <table> <thead> <tr> <th>
var body = document.body;

var table = document.createElement('table');
body.appendChild(table);
table.style.border = '1px solid skyblue';
table.style.textAlign = 'center';

var thead = document.createElement('thead');
table.appendChild(thead);

var tr = document.createElement('tr');
thead.appendChild(tr);


for (var i = 0; i < item.length; i++) {
  var th = document.createElement('th');
  tr.appendChild(th);
  th.innerText = item[i];
  th.style.width = '100px';
  th.style.backgroundColor = 'blue';
}
th = document.createElement('th');
tr.appendChild(th);
th.innerText = '刪除';
th.style.backgroundColor = 'blue';
th.style.width = '100px';


// <body> <table> <tbody> <tr> <td>
var tbody = document.createElement('tbody');
table.appendChild(tbody);

for (var i = 0; i < tmpDate.length; i++) {
  var tr = document.createElement('tr');
  tbody.appendChild(tr);
  for (key in tmpDate[i]) {
    var td = document.createElement('td');
    tr.append(td);
    td.innerText = tmpDate[i][key];
  }
  td = document.createElement('td');
  tr.append(td);
  td.innerHTML = '<a href="javascript:;">Del</a>';
}

var links = tbody.querySelectorAll('a');

for (var i = 0; i<links.length; i++) {
  links[i].addEventListener('click', function () {
    // <tbody> <tr> <td> <a>
    tbody.removeChild(this.parentNode.parentNode);
    return false
  })
}
```


### document.write()
> - document.write('內容')
>   - 簡單說就是在參數寫 innerHTML 後就會加載到頁面
>   - 問題: 如果是在頁面加載完畢==後==調用, 整個頁面會被重置成 write 裡的參數

```htmlmixed=
<body>
  <input type='button' id='btn'/>

  <script>
  // 讀完這句後會在頁面加載一個包著 123 的 div 標籤
  document.write('<div>123</div>');

  var btn = document.getElementById('btn');
  btn.onclick = function () {
    // 整個頁面加載完後, 點擊body裡面那個button而觸發這個func時
    // 整個頁面會重新加載, 並只加載write裡的參數,
    // 也就是裝著 123 的 div 標籤
    document.write('<div>123</div>');
  }
  </script>
</body>
```


### 三個創建元素的方法
> - document.write()
>   - 有明顯缺點, 不列入比較

> #### 兩者主要差別在於執行速度
> - document.createElement()
> - element.innerHTML
>   - 當大量使用拼接時, 效率會比較慢
>   - 但如果是先生成一個array後, 一次附值, 那效率比 appendChild 高
  
> - 以下三種方法皆在div內添加一模一樣的 a 標籤
>   - 效率: `innerHTML array` > `createElement()` >>>>>>>> `innerHTML 拼接`
```htmlmixed=
<!-- createElement()-->
<!-- 大概在 100-200 豪秒之間 -->
<body>
  <div></div>

  <script>
  var t1 = new Date();
  var div = document.querySelector('div');
  for (i=0; i<10000; i++) {
    var a = document.createElement('a');
    a.innerHTML = '測試';
    a.href = 'javascript:;'
    div.append(a);
  }
  var t2 = new Date();
  console.log(t2.getTime() - t1.getTime());
  </script>
</body>
```


```htmlmixed=
<!-- innerHTML 拼接 -->
<!-- 190690 豪秒 --> <!-- 久到懷疑人生 -->

<body>
  <div></div>

  <script>
  var t1 = new Date();
  var div = document.querySelector('div');
  for (i=0; i<10000; i++) {
    div.innerHTML += '<a href="javascript:;">測試</a>'
  }
  var t2 = new Date();
  console.log(t2 - t1);
  </script>
</body>
```

```htmlmixed=
<!-- innerHTML array -->
<!-- 50 豪秒左右 -->
<body>
  <div></div>

  <script>
  var t1 = new Date();
  var div = document.querySelector('div');
  var arr = [];
  for (i=0; i<10000; i++) {
    arr.push('<a href="javascript:;">測試</a>')
  }
  div.innerHTML = arr.join('');
  var t2 = new Date();
  console.log(t2 - t1);
  </script>
</body>
```
> - 對創建的元素做其他操作的時機不同
>   - `element.innerHTML` 必須在網頁讀取完後再找到DOM對象來操作
>   - `document.createElement()` 可以在創建元素的同時直接操做

```javascript=
var body = document.body;
var arr = [];
// 創建元素
for (var i = 0; i<3; i++) {
  arr.push('<button>clickMe</button>');
}
body.innerHTML = arr.join('');

// 獲取元素 DOM
var btn = body.getElementsByTagName('button');

// 操作
for (var i = 0; i < btn.length; i++) {
  btn[i].onclick = fn;
}
function fn() {
  console.log('hi');
}
```
```javascript=
var body = document.body
for (var i = 0; i < 3; i++) {
  // 創建元素
  var btn = document.createElement('button')
  // 直接操作
  btn.innerText = 'clickMe';
  btn.onclick = fn;
  // 添加到 DOM 樹 
  body.appendChild(btn);
}
function fn() {
  console.log('hi');
}
```
> - 釋放內存問題(內存泄露)
>   - 操作節點就是整個在玩他的位置, 清除也就都全部清掉了
>   - innerHTML 則是重新刻板, 只能刻出外表, 內在刻不到(事件等), 
>     把板清空時, 還可能有遺產(內存洩漏)

```htmlmixed=
<head>
  <meta charset='utf-8'>
  <style>
  div {
    height: 100px;
  }
  .one {
    background: skyblue;
  }
  .two {
    background: darkblue;
  }
  </style>
</head>
<body>
  <div class='one'>
    <button>123</button>
  </div>
  <div class='two'></div>
  <script>
  var one = document.querySelector('.one')
  var two = document.querySelector('.two')
  var btn = document.querySelector('button');


  btn.onclick = function () {
    console.log(1);
  }
  /* 這種方式無法帶走他原本有的東西, 如事件
   * 還有一個問題是one清空後, 原本的func可能會沒有清掉
  one.onclick = function () {
    two.innerHTML = one.innerHTML;
    one.innerHTML = '';
  }
  */

  one.onclick = function () {
    two.appendChild(btn);
  }
  </script>
</body>
```



### replaceChild

```htmlembedded=
<body>
    <p>1</p>
    <div>2</div>
    <script>
        const p = document.querySelector('p')
        const div = document.querySelector('div')
        div.parentNode.replaceChild(div, p)
        // 拿 div 去把 p 換掉
    </script>
</body>
```

### replaceWith
- 用什麼把調用的 DOM 替換掉
- `ChildNode.replaceWith((Node or DOMString))`
```htmlmixed=
<body>
    <div><p>1</p></div>
    <nav>3</nav>
    <script>
        const p = document.querySelector('p')
        const div = document.querySelector('div')
        const nav = document.querySelector('nav')
        p.replaceWith(nav)
        // 用 nav 把 p 給換了
        console.log(div.outerHTML)  // <div><nav>3</nav></div>
    </script>
</body>
```



## 事件

### 註冊事件
> - 傳統註冊: 
>   - on開頭 
>   - 同一元素跟同一事件, 只能設置一個處理函數, 後函數會覆蓋前函數
>     - `a.onclick = fn` `a.onclick = fn2`
>     - 因為等於重新賦值了
> - 方法監聽
>   - `eventTarget.addEventListener(type, listener[, useCapture])`
>     - type : 事件類型string( 必須加引號 ), 比如 'click' ( 注意! 沒有on )
>     - listener : 函數
>     - useCapture : 參數, Boolean, 默認 false
>   - w3c推薦
>   - 兼容問題: IE9+
>   - 同一元素跟同一事件, 可以設置多個處理函數, 按註冊順序依序執行

> - attachEvent
>   - `eventTarget.attachEvent(eventNameWithOn, callback)`
>   - 只有 IE10- 支持(非標準)

```htmlmixed=
<body>
  <input type='button' value='傳統觸發' />
  <input type='button' value='事件監聽' />

  <script>
  var btns = document.querySelectorAll('input');
  // 傳統觸發
  // 註冊兩個後, 只會觸發後面那個, 也就是 console.log('2');
  btns[0].onclick = function () {
    console.log('1');
  }
  btns[0].onclick = function () {
    console.log('2');
  }

  // 事件監聽
  // 註冊兩個後, 會依序觸發 3 4
  btns[1].addEventListener('click', function () {
    console.log('3');
  });
  btns[1].addEventListener('click', function () {
    console.log('4');
  });
  </script>
</body>
```
> - 兼容方法
```javascript=
function addEventListener(elem, eventName, fn) {
  // 判斷瀏覽器是否兼容 addEventListener
  if (element.addEventListener) { // 如果瀏覽器裡有 addEventListener
    elem.addEventListener(eventName, fn) // 那就調用
  } else if (element.attachEvent) { // 如果有 attachEvent
    elem.attachEvent('on'+eventName , fn) // 那就調用
  } else { // 真的都沒有, 那就用 onEventName
    // elem.onclick = fn 
    elem['on' + eventName] = fn
  }
}
```

### 刪除事件
> - 傳統刪除
>   - 直接把節點指向 null
>   - `eventTarget.eventNameWithOn = null;`
>     - `btns[0].onclick = null;`
> - IE9+ 事件監聽刪除
>   - `eventTarget.removeEventListener(type, listener[, useCapture])`
>     - `btns[1].removeEventListener('click', fn);`
>     - 也就是如果需要刪除事件, 那創建的時候不能使用匿名函數
> - IE8- 事件監聽刪除
>   - `eventTarget.detachEvent(eventNameWithOn, callback);`
>     - `btns[2].detachEvent('onclick', fnIE8);`
```htmlembedded=
<body>
  <input type='button' value='傳統觸發' />
  <input type='button' value='IE9+事件監聽' />
  <input type='button' value='IE8-事件監聽' />

  <script>
  var btns = document.querySelectorAll('input');
  // 傳統刪除
  btns[0].onclick = function () {
    console.log('1');
    btns[0].onclick = null;
  }

  // IE9+ 事件監聽刪除
  // 考慮到刪除時要指定func的變量, 所以建立時不要用匿名函數, 否則刪除時沒有變量可以用
  btns[1].addEventListener('click', fn);
  function fn() {
    console.log('2');
    btns[1].removeEventListener('click', fn);
  }
  
  
  // 我沒IE 所以沒有測試可行性
  btns[1].attachEvent('click', fnIE8);
  function fnIE8() {
    console.log('3');
    btns[2].detachEvent('onclick', fnIE8);
  }
  </script>
</body>
```
> - 兼容
```javascript=
function removeEventListener(elem, eventName, fn) {
  if (element.removeEventListener) {
    elem.removeEventListener(eventName, fn);
  } else if (element.detachEvent) {
    elem.detachEvent('on'+eventName, fn);
  } else {
    // elem.onclick  = null
    elem['on' + eventName] = null;
  }
}
```


### DOM 事件流
> - 事件傳播過程, 即「DOM 事件流」
>   - 捕獲階段( Capture Phase ): 網景提出, 指從頂層向下傳播的過程
>     - `Document` -> `Element html` -> `Element body` 
>        -> ... -> `Element <Target TagName>`
>   - 當前目標階段( Target Phase )
>     - `Element <Target TagName>`
>   - 冒泡階段( Bubbling Phase ): IE 提出, 指事件接收向上傳播的過程
>     - `Element <Target TagName>` -> ... -> `Element body`
>        -> `Element html` -> `Document`

> - JS code 只能執行其中一個(捕獲 或 冒泡)
> - 傳統註冊 ( `node.onclick` ) 跟 IE專屬 ( `attachEvent()` ) 只能得到冒泡階段
>   也就是說, 只有 `addEventListener()` 才能控制要使用哪種
>   - `true` : 捕獲
>   - `false` 或 空 : 冒泡 

> - 冒泡例外: focus 和 blur 不會冒泡
>   - 取代: focusin 跟 focusout 執行時間點一模一樣，但是會冒泡


> - [DOM 的事件傳遞機制：捕獲與冒泡](https://blog.techbridge.cc/2017/07/15/javascript-event-propagation/)

```htmlmixed=
<body>
    <div class='father'>
      <div class='son'>1</div>
    </div>

  <script>
  var father = document.querySelector('.father');
  var son = document.querySelector('.son');
  
  /* addEventListener 的第三個參數如果是 true
   * 表示事件捕獲階段(Document -> Target)調用處理程序 */
   // 先執行 father, 再執行 son
  /*
  father.addEventListener('click', function () {
    console.log('father');
  }, true)

  father.addEventListener('click', function () {
    console.log('son');
  }, true)
  */

  /* addEventListener 的第三個參數如果是 false 或 空
   * 表示事件冒泡階段(Target -> Document)調用處理程序 */
  // 先執行 son 才執行 father
  father.addEventListener('click', function () {
    console.log('father');
  })

  son.addEventListener('click', function () {
    console.log('son');
  })

  </script>
</body>
```

## 事件對象
> - 觸發事件時, 事件會傳一個實參給處理函數, 該參數指向該事件的對象
>   - 兼容問題: ie 678 使用 `window.event` 來獲取, 而不是形參

> - 事件對象是一個與該事件相關的數據集合, 
>   - 例如 滑鼠事件 裡有滑鼠座標等信息
>   - 例如 鍵盤事件 裡有鍵盤按鍵信息等
> - 故獲取方法就是在處理函數設一個形參來接收, 
> - 常用的形參名有 : event, evt, e

```htmlmixed=
</head>
  <body>
    <div class='one'>123</div>
    <div class='two'>456</div>
    <div class='three'>789</div>

  <script>
  var one = document.querySelector('.one');
  // function(event) 的event 接收來自 onlink 的數據
  one.onclick = function (event) {
    console.log(event);
  }

  var two = document.querySelector('.two');
  two.addEventListener('click', function (event) {
    console.log(event)
  })
  
  
  // 兼容 IE 678 寫法
  var three = document.querySelector('.three');
  three.onclick = function (e) {
    // 因為 IE 678 不是傳實參進func, 所以設形參也接收不到東西
    // 為了兼容性, 可以讓形參多一個 || window.event 的動作來處理
    // 如果瀏覽器沒有接到形參, 那就賦值 wondow.event
    e = e || window.event; 
    console.log(e);
  
  }
  </script>
  </body>
</body>
```

> - Q. 如果想對綁定函數傳參數怎辦?
```javascript=
function abc(arg1, arg2) {
    console.log(arg1, arg2)
}
xxx.addEventListener(`click`, abc, false);
ooo.addEventListener(`click`, ev=>{
    abc(123,4812);  // 綁一個回調後再回調裡調用就能傳了
}, false);

```



### 常見對象屬性和方法
> - `event.target`
>   - vs `this`
>     - `event.target` 返回的是觸發事件的對象
>     - `this` 返回的是綁定事件的對象       
>   - `event.target` 有兼容性問題(IE9+)
>     - IE 678 使用的是 `event.srcElement`
    
> - `event.currentTarget`
>   - vs. `this`
>     - `event.currentTarget` 跟 `this` 非常相似
>       差別在於前者有兼容性問題( IE9+ ), 所以使用上 this 用得比較多
```htmlmixed=

<body>
  <div class='one'>123</div>
  <ul>
    <li>123</li>
    <li>456</li>
  </ul>
  <div class='two'>456</div>
    
  <script>
  var one = document.querySelector('.one');
  // 這個例子差異看不太出來, 因為可以點擊的跟綁定的對象是同一個
  one.onclick = function (event) {
    console.log(event.target); // <div class='one'>123</div>
    console.log(this); // <div class='one'>123</div>
  }

  var ul = document.querySelector('ul');
  // 這個例子就很明顯
    // event.target 返回的是誰觸發對象的, 點擊123就返回123, 點擊456就返回456
    // this 返回綁定的對象, 這個事件是 ul 註冊的, 所以 this 指向這個 ul
  ul.onclick = function (event) {
    console.log(event.target); // <li>123</li> 或 <li>456</li>
    console.log(this); // <ul>...</ul>
    
    // currentTarget 與 this 非常相似的屬性, 有兼容問題, 所以使用上較少
    console.log(event.currentTarget); // <ul>...</ul>
  }

  
  // 兼容 IE678
  var two = document.querySelector('.two');
  two.onclick = function (e) {
    e = e || window.event; // IE678 獲取對象的方法
    var target = e.target || e.srcElement; // IE678獲取target屬性的方法
    console.log(target);
  }
  
  </script>
</body>
```

> - `e.eventPhase` : 事件的階段
>   - `1` 是捕獲階段; `2` 是目標階段; `3` 是冒泡階段
```htmlmixed=
<head>
  <meta charset='utf-8'>
  <style>
  .one {
    height: 100px;
    width: 100px;
    background: blue;
  }
  .two {
    height: 60px;
    width: 60px;
    background: skyblue;
  }
  .three {
    height: 30px;
    width: 30px;
    background: darkblue;
  }
  </style>
</head>
<body>
  <div class='one'>	
    <div class='two'>
      <div class='three'>
      </div>
    </div>
  </div>
  <script>
  var one = document.querySelector('.one');
  one.addEventListener('click', function (e) {
    console.log(e.eventPhase);
    console.log(e.target);
    console.log(e.currentTarget);
    console.log(this);
  }, true)
  
  // 當 one 是捕獲階段(true)時
    // 點擊 <three> 的 eventPhase 為 1; 點擊 <two> 為 1; 點擊 <one> 為 2
  // 當 one 是冒泡階段(false)
    // 點擊 <three> 的 eventPhase 為 3; 點擊 <two> 為 3; 點擊 <one> 為 2
  </script>
</body>
```



> - `event.type` : 返回事件類型(不帶 on )
```htmlmixed=
<body>
  <div class='one'>123</div>
  
  <script>
  var div = document.querySelector('div');
  /*
  div.onclick = function (e) {
    console.log(e.type); // click 不帶on
  }
  */
  
  
  div.addEventListener('click', fn); // click
  div.addEventListener('mouseover', fn); // mouseover
  div.addEventListener('mouseout', fn); // mouseout

  function fn(e) {
    // 返回觸發事件類型
    console.log(e.type);
  }
  </script>
</body>
```

> #### 阻止默認行為
> - `addEventListener()`
>   - `event.preventDefault()`
>   - 兼容問題( 兼容問題 (IE9+) ) : 
>     - IE678 本來就不認識 `addEventListener` , 
>     - 當然也就不用討論到後面 `preventDefault()`

> - 傳統註冊
>   - `event.preventDefault()`
>     - 兼容問題( IE9+ )
>   - `event.returnValue = false;` (非官方)
>   - `return false`
>     - 沒有兼容問題
>     - return 就返回了, 所以 return 後面的東西就都不會執行

> - IE 678
>   - 傳統註冊 + `event.returnValue = false`
>   - 傳統註冊 + `return false`
   
```htmlmixed=
<head>
  <meta charset='utf-8'>
  <style>
  </style>
</head>
<body>
  <a href='#'>123</a>
  <a href='#'>456</a>
  <script>
  var as = document.querySelectorAll('a');
  as[0].addEventListener('click', function (e) {
    // addEventListener 註冊方法裡只有這種阻止默認行為的方式
    e.preventDefault();
    // return false 沒有效用
  })

  as[1].onclick = function (e) {
    e = e || window.event
    // e.preventDefault(); // 第一種
    // e.returnValue = false; // 第二種
    return false // 第三種, 後面語句不執行
    console.log('haha');
  }
  </script>
</body>
```

### 阻止事件冒泡
> - 事件冒泡, 從具體觸發一直到 document, 一路觸發事件到底
> - 阻止事件冒泡就是觸發完後停止, 不再繼續向上觸發

> - `stopPropagation()`(標準)
>   - 兼容問題(IE9+)
> - `e.cancelBubble = true;` (非標準), 
>   - IE8- 也都可以用
>   - 但是如果為了兼容 IE678 的話, 記得不能用 addEventListener

```htmlmixed=
<body>
  <div class='fa' style='padding: 20px; border: 1px solid red;'>
    <div class='son' style='border: 1px solid blue;'>123</div>
  </div>

  <script>
  var fa = document.querySelector('.fa');
  var son = document.querySelector('.son');

  // 我只有對 son 指定終止冒泡, 所以點擊son 輸出son後會停止
  son.addEventListener('click', function (e) {
    console.log('son');
    // e.stopPropagation();
    e.cancelBubble = true;
  }, false)

  // 我沒有對 father 指定終止冒泡, 所以點擊 father 會輸出 father 跟 document
  fa.addEventListener('click', function () {
    console.log('father');
  }, false)

  document.addEventListener('click', function () {
    console.log('document');
  }, false)
  </script>
</body>
```

```javascript=
// 兼容 IE678 寫法參考
fa.onclick = function (e) {
  // 如果瀏覽器有使用形參傳對象, 且認識 stopPropagation
  if (e && e.stopPropagation) {
    e.stopPropagation(); // 調用
  } else { // 否則就是 IE678 
    window.event.cancelBubble = true;
  }
}
```

> - `stopImmediatePropagation` 阻止後面同一事件所有即將被執行的 handler 執行 ( IE9+ )
>   - 既然後面 handler 不執行了，所以也有阻止冒泡的效果
>   - 與 stopPropagation 的區別就在於後者只阻止父層以上的同事件 handler 
```htmlembedded=
<body>
    <style>
        .fa {
            padding: 100px;
            background: blue;
        }
        .son {
            height: 100px;
            width: 100px;
            background: red;
        }
    </style>
    <div class="fa">
        <div class="son"></div>
    </div>
    <script>
        const oFa = document.querySelector('.fa').addEventListener('click', (ev) => {
            console.log('fa')
        })
        const oSon = document.querySelector('.son')
        
        oSon.addEventListener('click', (ev) => {
            console.log('son-1')
            // ev.stopPropagation();
            ev.stopImmediatePropagation();
                // 如果是 stopPropagation, 那麼 son-2 還會執行
                // stopImmediatePropagation 直接斷在這， 連 son-2 也不會執行

        })
        oSon.addEventListener('click', (ev) => {
            console.log('son-2')
        })
    </script>
</body>
```



### 事件委託

> - 以前想要操作子節點時, 都會對每個 node 註冊
> - 事件委託是對父節點操作DOM, 
>   如果想對子節點操作, 可利用觸發事件返回對象中的 target 屬性來找到對象

```htmlmixed=
<body>
  <ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
  </ul>

  <script>
  /* 以前的作法: 對每個 li 做DOM 操作
  var lis = document.querySelectorAll('li');
  for (i=0; i<lis.length; i++) {
    lis[i].addEventListener('click', function (e) {
      console.log(this)
    })
  }
  */
  // 利用冒泡, 只需對父元素操作DOM, 再利用 target 屬性來找到對象
  var ul = document.querySelector('ul');
  ul.addEventListener('click', function (e) {
    console.log(e.target);
    e.target.style.backgroundColor = 'skyblue';
  })
  </script>
</body>
```



### 常用鼠標事件

> - `contextmenu` : 控制上下選單, 對於鼠標事件來說就是右鍵選單
> - `selectstart` : 控制選取

```htmlmixed=
<body>
  <p>123</p>

  <script>
    // 禁止右鍵
    // 控制選單 -> 取消 默認行為(可以拉選單)
    document.addEventListener('contextmenu', function (e) {
      e.preventDefault()
    })
    // 禁止選取
    // 控制開始選取 -> 取消 默認行為(可以選取)
    document.addEventListener('selectstart', function (e) {
      e.preventDefault()
    })
  </script>
</body>
```

### 鼠標事件對象

> #### clientX 與 clientY
> - clientX : 返回相對於 頁面 ==可視區== 的 X 座標
> - clientY : 返回相對於 頁面 ==可視區== 的 Y 座標
> #### pageX 與 pageY
> - pageX : 返回相對於 ==文檔頁面== 的 X 座標
> - pageY : 返回相對於 ==文檔頁面== 的 Y 座標
>   - 有兼容性問題: IE9 以後才支援
>   - 解法就是 client + 頁面滾動距離 = page
> #### screenX 與 screenY
> - screenX : 返回相對於 ==電腦螢幕== 的 X 座標
> - screenY : 返回相對於 ==電腦螢幕== 的 Y 座標

```htmlmixed=
<body>
  <script>
  var body = document.querySelector('body');
  body.style.height = '3000px';
    document.addEventListener('mousemove', function (e) {
      console.log('--------');
      console.log(`e.clientX:${e.clientX}, e.pageX:${e.pageX}, e.screenX: ${e.screenX}`);
      console.log(`e.clientY:${e.clientY}, e.pageY:${e.pageY}, e.screenY: ${e.screenY}`);
      console.log('--------');
    })
  </script>
</body>
```

### 案例: 圖片跟隨滑鼠
> - `mousemove` : 滑鼠移動事件
> - document 註冊這個比較方便
> - 圖片不佔位, 且可以控制 XY 移動 -> absolute
>   - 透過 mousemove 的 pageXY 或 clientXY 來拿滑鼠座標
>   - 圖片left, top 賦予滑鼠座標, 記得加單位(px)
```htmlmixed=
<body>
  <img src='https://api.fnkr.net/testimg/20x20/ccc'>
  
  <script>
  // 1. 拿到圖片
  var img = document.querySelector('img');
  // 2. 設定成絕對定位
  img.style.position = 'absolute';
  
  // 3. mousemove
  document.addEventListener('mousemove', function (e) {
    console.log(e.pageX, e.pageY);
    // 4. 賦值
    img.style.left = e.pageX-10 + 'px';
    img.style.top = e.pageY-10 + 'px';
  })
  </script>
</body>
```

### 滾動距離
> #### 元素滾動距離
> - `elem.scrollTop` `elem.scrollLeft`
> #### 頁面滾動距離
> - DOM
>   - `element.body.scrollTop` `element.body.scrollLeft`
>   - `document.documentElement.scrollTop` `document.documentElement.scrollLeft`
>     - `document.documentElement` 就是 html
>   - 這兩種有兼容性問題, 有些瀏覽器上面那個可以, 有些是下面這個可以
>     - 191208 我的 `Chrome: Version 78.0.3904.108` 是用從 html 獲取
> - BOM
>   - `window.pageXOffset` `window.pageYOffset`
```htmlmixed=
<head>
  <meta charset='utf-8'>
  <style>
  body {
    height: 3000px;
    width: 2000px;
  }
  div {
    height: 100px;
    width: 50px;
    border: 1px solid red;
    overflow: auto;
  }
  </style>
</head>
<body>
  <div>
    我是內容
    我是內容
    我是內容
    我是內容
    我是內容
    我是內容
    我是內容
    我是內容
    我是內容
    我是內容
    我是內容
  </div>
  <script>
  // 元素滾動距離
  var div = document.querySelector('div');
  div.addEventListener('click', function () {
    console.log(div.scrollTop, div.scrollLeft);
  }, true)


  // 頁面滾動距離
  document.addEventListener('click', function () {
    console.log(document.body.scrollTop, document.body.scrollLeft);
    console.log(document.documentElement.scrollTop, document.documentElement.scrollLeft);
    console.log(window.pageYOffset, window.pageXOffset);
  })


  // DOM 文檔的根元素, 也就是 html 標籤
  console.log(document.documentElement);
  </script>
</body>
```
> - DOM.scroll & e.page 兼容
```javascript=
// 測試環境設置而已, 讓頁面有滾動條
document.body.style.width = '2000px';
document.body.style.height = '3000px';

// scroll 兼容
function getScroll() {
  scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
  scrollLeft = document.body.scrollLeft || document.documentElement.scrollLeft;

  return {
    scrollTop: scrollTop,
    scrollLeft: scrollLeft,
  }
}

// e.page 兼容
function getPage(e) {
  var pageX = e.pageX || e.clientX + getScroll().scrollLeft;
  var pageY = e.pageY || e.clientY + getScroll().scrollTop;
  return {
    pageX: pageX,
    pageY: pageY,
  }
}


document.addEventListener('click', function (e) {
  // console.log(getScroll().scrollTop, getScroll().scrollLeft);
  // console.log(window.pageYOffset, window.pageXOffset);
  
  // event 兼容
  var e = e || window.event;
  console.log(getPage(e).pageX, getPage(e).pageY)
})
```

> #### `ondblclick`
> - 雙擊觸發
> - `window.getSelection? window.getSelection().removeAllRanges(): document.selection.empty();` : 取消選取文字
```htmlmixed=
<body>
  <div>1</div>
  <script src='test.js'></script>
</body>
```
```javascript=
var div = document.querySelector('div');

// 雙擊觸發
div.addEventListener('dblclick', function () {
  console.log(1);
  // 雙集會選取文字, 判斷是否選取, 有就取消
  window.getSelection? window.getSelection().removeAllRanges(): document.selection.empty();
})
```




### 常用鍵盤事件
> - `onkeyup` : 按下按鍵鬆開時觸發
> - `onkeydown` : 按下按鍵時觸發
> - `onkeypress` : 按下按鍵時觸發
>   - 不識別功能鍵, 如ctrl, shift...等

> - 執行順序: `onkeydown` -> `onkeypress` -> `onkeyup` 
```htmlmixed=
<body>
  <script>
  /*document.onkeyup = function () {
    console.log('keyup')
  }*/
  
  document.addEventListener('keyup', function () {
    console.log('keyup');
  })
  document.addEventListener('keydown', function () {
    console.log('keydown');
  })
  document.addEventListener('keypress', function () {
    console.log('keypress');
  })
  </script>
</body>
```

### 鍵盤事件對象
> - `keyCode` : 返回按鍵的ASCII
>   - `keyup` 跟 `keydown` 不區分大小寫, 但可識別功能鍵
>   - `keypress` 能區分大小寫, 但不識別功能鍵
```htmlmixed=
<body>
  <input type='text'/>
  <script>
  document.addEventListener('keyup', function (e) {
    console.log('keyup: ' + e.keyCode);
    console.log('------')
  })
  document.addEventListener('keydown', function (e) {
    console.log('keydown: ' + e.keyCode);
  })
  document.addEventListener('keypress', function (e) {
    console.log('keypress: ' + e.keyCode);
  })
  </script>
</body>
```

### 練習: 按 S 鍵, 讓輸入框獲得焦點
> - 輸入框獲得焦點: `input.focus()`
```htmlmixed=
<body>
  <input type='text'/>
  
  <script>
  var input = document.querySelector('input');
  /* 這裡使用 keyup 比較適合, 
   * 因為 keydown 會連續觸發, 造成按s時, 除了觸發焦點外, 還馬上在上面輸入了一個 s
   * keyup 是彈起來才觸發一次
  document.addEventListener('keydown', function (e) {
    console.log(e.keyCode);
    if (e.keyCode === 83) {
      input.focus();
    }
  })
  */
  document.addEventListener('keyup', function (e) {
    console.log(e.keyCode);
    if (e.keyCode === 83) {
      input.focus();
    }
  })
  </script>
</body>
```

### 練習: 輸入框的字體放大鏡
<img src='https://i.imgur.com/BCo0q3z.png' style='width: 200px;'/>

> - 利用 keyup 來感應輸入框有沒有在打字
>   - 不適合 keydown 跟 keypress
>     - 因為這兩個在按下按鈕時就馬上觸發了, input.value 都還沒把內容寫進去
>     - 如此會導致少一個字的問題
>       <img src='https://i.imgur.com/JUUAee8.png' style='width: 150px;'/>

> - 把 input.value 內容丟到 innerText 裡
> - 如果 `input.value === ''` 就隱藏盒子
> - 失去焦點的時候, 隱藏放大鏡
> - 獲得焦點的時候, 輸入框有東西就顯示放大鏡

```htmlmixed=
<head>
  <meta charset='utf-8'>
  <style>
  .fa {
    position: relative;
    height: 300px;
    width: 300px;
    margin: 300px auto;
  }
  /* 放大盒 */
  .big {
    height: 50px;
    width: 100px;
    border: 1px solid red;
    box-shadow: 0px 5px 8px 0px gray;
    margin-bottom: 10px;
    position: relative;
    display: none; /* 一開始隱藏 */
    position: absolute; /* 為了盒子不佔位, 所以做定位 */
    top: -60px;
  }
  /* 三角形小圖標 */
  .big::after {
    content: '';
    width: 0;
    height: 0;
    line-height: 0;
    font-size: 0;

    border: 8px solid transparent;
    border-top-color: white;

    position: absolute;
    bottom: -16px;
    left: 5px;
  }
  </style>
</head>
<body>
  <div class='fa'>
    <div class='big'></div>
    <input type='text'></input>
  </div>
  <script>
  var input = document.querySelector('input');
  var div = document.querySelector('.big');
  /* keydown跟keypress按下按鈕時馬上觸發, 
   * 字都還沒進到 input.value 裡, 所以會產生少一個字的問題
  input.addEventListener('keydown', function () {
    div.innerText = input.value;
  */
  input.addEventListener('keyup', function () {
    // 把 input 框的內容丟到放大盒裏
    div.innerText = input.value;
    // 判斷一下 input 框有沒有東西
    if (input.value.length === 0) {
      div.style.display = 'none'; // 沒東西就隱藏
    } else {
      div.style.display = 'block'; // 有東西就顯示
    }
  })

  // 輸入匡失去焦點的時候隱藏放大盒
  input.addEventListener('blur', function () {
    div.style.display = 'none';
  })
  
  // 獲得焦點得時候, 有東西就顯示
  input.addEventListener('focus', function () {
    if (input.value !== '') {
      div.style.display = 'block';
    }
  })
  </script>
</body>
```


### 練習: 只能輸入數字與刪除的文本框
> - `keyup` 不適合, 因為觸發 keyup前, 內容已經寫進去了
> - 所以用 `keydown` 比較好

`<input type="text"/>`
```javascript=
var txt = document.querySelector('input');
txt.onkeydown = function (e) {
  console.log(e.keyCode);
  // 0 = 48, 9 = 57, delete = 8
  if ((e.keyCode < 48 || e.keyCode > 57) && e.keyCode !== 8) {
    return false
  }
}
```

## 表單域 [onchange](https://developer.mozilla.org/zh-CN/docs/Web/Events/change) [oninput](https://developer.mozilla.org/zh-CN/docs/Web/Events/input) 事件的差別
- oninput 是只要輸入框一改變, 即觸發事件
- onchange 是輸入框失去焦點時，觸發事件
```htmlmixed=
<body>
    <!-- 這裡的東西要失去焦點才觸發, 我感覺 select 沒有差別 -->
    <input type="text" onchange="test(event)"/>
    <select onchange="test(event)">
        <option>1</option>
        <option>2</option>
        <option>3</option>
    </select>
    <textarea onchange="test(event)"></textarea> <br/>

    <!-- 這裡的東西只要改變文本框的內容馬上觸發 -->
    <input type="text" oninput="test(event)"/>
    <select oninput="test(event)">
        <option>1</option>
        <option>2</option>
        <option>3</option>
    </select>
    <textarea oninput="test(event)"></textarea>
</body>
<script>
    function test(ev) {
        
        console.log(ev.target.value);
    }
</script>
```







