owned this note
owned this note
Published
Linked with GitHub
# Dom
###### tags: `Tag(Dom)`
### 什麼是 `DOM`?
>[參考資料](https://ithelp.ithome.com.tw/articles/10191666) 重新認識 JavaScript: Day 11 前端工程師的主戰場:瀏覽器裡的 JavaScript
Dom簡單來說就是 ==javascript跟html/瀏覽器之間的橋樑==,瀏覽器提供了這個橋樑(Dom),讓我們去改變畫面
<font color="#3ca59d">**DOM API 定義了讓 JavaScript 可以存取、改變 HTML 架構、樣式和內容的方法,甚至是對節點綁定的事件。**</font>
<font color="#3ca59d">**JavaScript 透過 DOM 提供的 API 來 ==對 HTML 做存取與操作==。**</font>
```javascript=
// 根據傳入的值,找到 DOM 中 id 為 'xxx' 的元素。
document.getElementById('xxx');
// 針對給定的 tag 名稱,回傳所有符合條件的 NodeList 物件
document.getElementsByTagName('xxx');
// 針對給定的 class 名稱,回傳所有符合條件的 NodeList 物件。
document.getElementsByClassName('xxx');
// 針對給定的 Selector 條件,回傳第一個 或 所有符合條件的 NodeList。
document.querySelector('xxx');
document.querySelectorAll('xxx');
```
:::warning
``` c
NodeList 物件是節點的集合,可藉由 Node.childNodes 屬性
或 document.querySelectorAll() 等方法取得。
NodeList 雖然有著與陣列相似的特性,但不是陣列,所以也不會有陣列相關的
method 可以使用 (如 map、filter 等)。
```
:::
---
### DOM 節點的選取
>[參考資料](https://ithelp.ithome.com.tw/articles/10191765) 重新認識 JavaScript: Day 12 透過 DOM API 查找節點
當我們要存取 HTML 時,都==從 document 物件開始==。
常見的 DOM 選取方法有:
```javascript=
// 根據傳入的值,找到 DOM 中 id 為 'xxx' 的元素。
document.getElementById('xxx');
<scrip> //注意! 這邊Element沒有s,因為id只能用一個!
const elements = document.getElementById('block') // 抓到指定的Id的東西
console.log(elements[0])
</scrip>
// 針對給定的 tag 名稱,回傳所有符合條件的集合
document.getElementsByTagName('xxx');
<scrip>
const elements = document.getElementsByTagName('div') //抓到所有屬於這個tag的元素
console.log(elements[1])
</scrip>
// 抓到指定的class的東西
// IE9 後開始支援
document.getElementsByClassName('xxx');
<scrip>
const elements = document.getElementsByClassName('block')
console.log(elements[0])
</scrip>
// 針對給定的 Selector 條件,回傳匹配到的第一個元素
// IE8 後開始支援
document.querySelector('xxx');
<scrip> // 只會回傳匹配到的第一個元素
const elements = document.querySelector('#block') // 抓到css得選擇器,可以選Id、class、div
console.log(elements[0])
</scrip>
// 針對給定的 Selector 條件,回傳所有符合條件的 NodeList
document.querySelectorAll('xxx');
```
:::warning
```css=
document.querySelector 與 document.querySelectorAll
可以用 「CSS 選擇器」 (CSS Selectors) 來取得「第一個」
或「所有」符合條件的元素集合 (NodeList)。
```
:::
---
### DOM 節點間的查找遍歷 (Traversing)
>[參考資料](https://ithelp.ithome.com.tw/articles/10191765) 重新認識 JavaScript: Day 12 透過 DOM API 查找節點
==DOM 節點有分層的概念==,節點與節點之間的關係,大致上可以分成兩種:
```markedown
父子關係:
除了 document 之外,每一個節點都會有個上層的節點,我們通常稱之
為「父節點」 (Parent node),而相對地,從屬於自己下層的節點,
就會稱為「子節點」(Child node)。
兄弟關係:
有同一個「父節點」的節點,那麼他們彼此之間就是「兄弟節點」(Siblings node)。
```
![](https://i.imgur.com/8pq9UmZ.png)
### 選到前一個元素
使用 `previousElementSibling`
###### `範例`
```javascript=
document
.querySelector('.list').previousElementSibling.innerHTML;
```
### `document.querySelectorAll`
>[參考資料](https://ithelp.ithome.com.tw/articles/10191765) 重新認識 JavaScript: Day 12 透過 DOM API 查找節點
>[參考資料](https://jmln.tw/blog/2017-07-07-vanilla-javascript-dom-manipulation.html) 都 2017 年了,學學用原生 JS 來操作 DOM 吧
`document.getElementById` 以及 `document.querySelector `因爲 ==取得的一定只會有一個元素/節點== ,所以不會有 index 與 length 屬性。
<font color="#3ca59d">**`document.querySelectorAll` 回傳 「NodeList」。**</font>
「NodeList」除了 HTML element 節點,也包含文字節點、屬性節點等。 雖然不能使用陣列型別的 method,但 ==可以用「陣列索引」的方式來存取內容==
`NodeList` 在大部分情況下是即時更新的,但 ==透過 `document.querySelector / document.querySelectorAll` 取得的 NodeList 是靜態的==。
```javascript=
<div id="outer">
<div id="inner">inner</div>
</div>
<script>
// <div id="outer">
var outerDiv = document.getElementById('outer');
// 所有的 <div> 標籤
var allDivs = document.querySelectorAll('div');
console.log(allDivs.length); // 2
// 清空 <div id="outer"> 下的節點
outerDiv.innerHTML = '';
// document.querySelector 回傳的是靜態的 NodeList,不受 outerDiv 更新影響
console.log(allDivs.length); // 2
</script>
```
#### 把 `querySelectorAll()` 回傳的 `NodeList` 轉成 Array 之後,就能用 `forEach()` 方式走訪每個元素。
```javascript=
Array.from(allElements).forEach(element => { // do something... })
// IE 還不支援 Array.from(),可以用:
Array.prototype.forEach.call(allElements, element => {
// do something...
})
// 更短的寫法:
[].forEach.call(allElements, element => {
// do something...
})
```
---
### 順利選到元素後 來試看看改變它!
#### 如何改變`css`
`element.style `後面==接想要改的東西==
```htmlmixed=
<body>
<div id='block'>
hello~
</div>
<script>
const element = document.querySelector('#block')
element.style.background = 'red'
</script>
</body>
```
:::success
==`.` 點後面不能接 `-`==
有兩種方式可以解決:
第一種 `['padding-top'] `用字串包起來,不會有不合格語法的問題
```html
<body>
<div id='block'>
hello~
</div>
<script>
const element = document.querySelector('#block')
elemaent.style['padding-top'] = '10px'
</script>
</body>
```
第二種 駝峰式寫法,小寫開頭碰到一個單字就大寫
```html
<body>
<div id='block'>
hello~
</div>
<script>
const element = document.querySelector('#block')
elemaent.style.paddingTop = '10px'
</script>
</body>
```
:::
#### 如何改變`class`?
>[參考資料](https://jmln.tw/blog/2017-07-07-vanilla-javascript-dom-manipulation.html) 都 2017 年了,學學用原生 JS 來操作 DOM 吧
要修改元素的 class,可以==用方便的 `classList` 操作==。
```javascript=
oneElement.classList.add('baz')
oneElement.classList.remove('baz')
oneElement.classList.toggle('baz')
// 檢查是否有指定的 class
oneElement.classList.contains('baz')
```
第一種 讓javascript動態把class加上去
``` c
<body>
<div id='block'>
hello~
</div>
<script>
const element = document.querySelector('#block')
element.classList.add('active')
</script>
</body>
```
第二種 如何移除class
``` c
<body>
<div id='block' class='main'>
hello~
</div>
<script>
const element = document.querySelector('#block')
element.classList.add('active')
element.classList.remove('main')
</script>
</body>
```
第三種 ==toggle就像是開關==(本來沒有的話,會便有; 本來有的話,會便沒有)
``` c
<body>
<div id='block' class='main'>
hello~
</div>
<script>
const element = document.querySelector('#block')
element.classList.add('active')
element.classList.toggle('main')
</script>
</body>
```
#### 如何改變內容(修改 DOM)?
>[參考資料](https://jmln.tw/blog/2017-07-07-vanilla-javascript-dom-manipulation.html) 都 2017 年了,學學用原生 JS 來操作 DOM 吧
```javascript=
// 在 element1 裡插入一個 element2
element1.appendChild(element2)
// 在 element1 裡的 element3 之前插入一個 element2
element1.insertBefore(element2, element3)
// 複製 DOM
const newElement = oneElement.cloneNode()
element1.appendChild(newElement)
// 建立新的 DOM
const newElement = document.createElement('div')
const newTextNode = document.createTextNode('hello world')
// 移除 DOM,需要參照到親元素
parentElement.removeChild(element1)
// 自己移除自己
element1.parentNode.removeChild(element1)
```
第一種 `innerText`
``` c
<body>
<div id='block'>
yo
<a>hello~</a>
</div>
<script>
const element = document.querySelector('#block')
console.log(element.innerText)
</script>
</body>
```
第二個`innerHTML` 插入HTML
可以將元素內的html==重新覆蓋寫入== 新的html。
```javascript=
var el = document.getElementById('main');
var str = '<h1 class="blue">1234</h1>'
el.innerHTML = str;
```
第三個`outerHTML` 印出整段文字包含標籤
``` c
<body>
<div id='block'>
yo
<a>hello~</a>
</div>
<script>
const element = document.querySelector('#block')
console.log(element.outerHTML)
</script>
</body>
```
---
### 插入與刪除元素
#### 刪除元素
``` c
<body>
<div id='block'>
yo
<a>hello~</a>
</div>
<script>
const element = document.querySelector('#block a')
//刪除<a>
element.removeChild(document.querySelector('a'))
</script>
</body>
```
#### 新增元素
>[參考資料](https://pjchender.blogspot.com/2017/06/js-node-element-appenchild-disappear.html) Node Element 在 appendChild 後消失(disappear)!?
``` c
<body>
<div id='block'>
yo
<a>hello~</a>
</div>
<script>
const element = document.querySelector('#block a')
const item = document.creatTextNode('123')
element.appendChild(item)
</script>
</body>
```
:::info
如果 appendChild 使用時,append 的是一個已存在的 node 時,它==會做的是搬移,而非複製==。
``` c
// 把 innerElement append 到 block1 上
block1.appendChild(innerElement)
// 這時候 innerElement 已經是存在的 Node 了,所會把這個 Node 進行"搬移",於是原本在 .block1 的 innerElement 被搬到 .block2
block2.appendChild(innerElement)
// 同理,原本在 .block2 的 innerElement 被搬到 .block3
block3.appendChild(innerElement)
```
使用 `appendChild()` 時,對於現存的 Node 它會採用搬移的方式,讓如果想要==複製一整個 element== 呢?
可以使用 `Node.cloneNode()` 這個方法
``` c
const buttonClone = document.querySelector('#cloneNode')
buttonClone.addEventListener('click', function () {
let cloneElement = innerElement.cloneNode(true) // 真的複製這個 Node
if (i === 0) {
block1.appendChild(cloneElement)
} else if (i === 1) {
block2.appendChild(cloneElement)
} else {
block3.appendChild(cloneElement)
}
i = (i + 1) % 3
})
```
==先使用 Node.cloneNode() 這個方法複製 Node Element,再使用 appendChild==
:::
#### `createElement` - 插入dom元素
建立一個新的DOM元素,然後再使用 appendChild 新增子節點,並==不會覆蓋原有的DOM元素==。
``` c
<h1 class="title">
<em>titile</em>
</h1>
<script >
// 建立元素
var sonElement = document.createElement("a");
sonElement.setAttribute('href','www.facebook.com');
sonElement.textContent = '前往Facebook';
// 增加子節點
var fatherElement = document.querySelector('.title');
fatherElement.appendChild(sonElement);
</script>
```
---
### `Attribute` - 增加標籤屬性
>[參考資料](https://kanboo.github.io/2017/12/30/JS-studynotes/) JS-基礎用法
#### `setAttribute` 設定 標籤屬性
```javascript=
var el = document.querySelector('.titleClass a');
el.setAttribute('href','http://www.yahoo.com.tw');
```
#### `getAttribute` 取得 標籤屬性
```javascript=
var el3 = document.querySelector('.titleClass a').getAttribute('href');
console.log(el3);
```
---
### `event listener `事件監聽
>[參考資料](https://jmln.tw/blog/2017-07-07-vanilla-javascript-dom-manipulation.html) 都 2017 年了,學學用原生 JS 來操作 DOM 吧
==一但事件被觸發就會執行後面的function==
``` c
<body>
<div id='block'>
yo
<a>hello~</a>
</div>
<script>
const element = document.querySelector('#block')
element.addEventListener('click',function (){
alert('click!')
}
</script>
</body>
//常見的寫法 匿名函式
```
:::info
`event(e)` 是什麼??
瀏覽器在呼叫function時會傳入一個參數 ==!名稱可以自己取==
function (e)
e => 瀏覽器會帶入一些資訊,可以 ==利用這個資訊/變數拿到跟事件相關的東西==
``` c
<body>
<div id='block'>
yo
<input/>
<a>hello~</a>
</div>
<script>
const element = document.querySelector('input')
element.addEventListener('ketdown', function (e){
console.log(e.key)
})
</script>
</body>
//按了鍵盤 瀏覽器就會印出你按了哪些按鍵
//可以用e拿到一些額外的資訊
```
``` c
<style>
.active {
background: red;
}
</style>
</head>
<body>
<div id='block'>
yo
<input/>
<a>hello~</a>
<button class='change-btn'>change</button>
</div>
<script>
const element = document.querySelector('.change-btn')
element.addEventListener('click', function (e){
document.querySelector('body').classList.toggle('active')
})
</script>
</body>
//點了按鈕 背景顏色更改
//e 就是瀏覽器幫你傳的變數 裡面會有跟事件相關的資訊
```
:::
:::success
==`addEventListener` 有第三個參數==
``` markedown
第一個參數是事件名稱
第二個參數是callback function
第三個參數可以放一個布林變數,false的話就會被放在冒泡階段上;
true的話就會被放在捕獲階段上。
```
原本都沒有傳第三個參數的話,==預設就是false==
:::
---
### 事件代理(Event delegation)適合用的場合
>[參考資料](http://le0zh.github.io/2016/06/04/event-delegate-in-javascript/) js中的事件代理(event delegation)
* 待綁定的對象元素是==動態的==,需要頻繁的添加或者刪除等
* ==待綁定的元素很多==,比如一個100行的Table,需要對tr綁定事件;
* 被綁定的父級對象離目標對象的層級越少越好,否則會有性能問題,==盡量不要直接綁定到document或者body上==
* ==事件類型要注意是能冒泡的==,比如focus和blur本身是不會冒泡的
---
### 事件監聽優化 & `e.target`
>[參考資料](https://kanboo.github.io/2017/12/30/JS-studynotes/) JS-基礎用法
>[參考資料](https://eyesofkids.gitbooks.io/javascript-start-from-es6/content/part4/event.html) 事件處理
有時子元素可能要上千個,每個都要綁上監聽的話,效能不是很優,這時可==從父節點下手==,使用 `e.target.nodeName` 來取得是哪個元素觸發的。
###### `範例`
```
當有一個 ul 底下有多個 li 都要監聽的話,這時我們可以利用
addEventListener-事件氣泡 的原理,只要針對 ul 監聽,讓他往上冒泡,當到達 li 時,
這時我們就可以針對 li 做事了。
```
###### `原本寫法`
```javascript=
//取得ul底下的所有li元素
var list = document.querySelectorAll('.list li');
//forloop,將每個li元素綁上監聽事件(N次)
var len = list.length;
for(var i = 0;len>i;i++){
list[i].addEventListener('click',checkName,false)
}
function checkName(e){
console.log(e.target.textContent);
}
```
###### `優化寫法`
```javascript=
//取得ul元素
var list = document.querySelector('.list');
//將ul元素綁上監事件(一次)
list.addEventListener('click',checkName,false)
function checkName(e){
if(e.target.nodeName !== 'LI'){return}; // 判斷是否為li元素
console.log(e.target.textContent);
}
```
---
### `e.target` 與 `e.currentTarget` 的差別
>[參考資料](https://www.itread01.com/content/1545071047.html) e.currentTarget與e.target的區別
>[參考資料](https://javascript.info/bubbling-and-capturing#this-and-event-target) Bubbling and capturing
``` marked
在W3C標準的定義中,this會相等於event.currentTarget
this (=event.currentTarget) is the <form> element, because the handler runs on it.
event.target is the actual element inside the form that was clicked.
```
:::warning
![](https://i.imgur.com/DI39LOE.gif)
:::
---
### 表單事件處理 `onSubmit`
>[參考資料](https://morosedog.gitlab.io/web-html-20190531-web22/) HTML 表單資料驗證(一)
>[參考資料](https://morosedog.gitlab.io/web-html-20190603-web23/) HTML 表單資料驗證(二)
>[參考資料](https://pjchender.github.io/2017/11/01/web-%E8%A1%A8%E5%96%AE%E9%A9%97%E8%AD%89%E8%88%87%E5%8F%96%E5%80%BC-form-validation-and-value/) 表單驗證與取值 form validation and value
通常會用在==表單驗證==
``` c
<body>
<form class='login-form'>
<div>
username: <input name='username'/>
</div>
<div>
password: <input name='password' type='password'/>
</div>
<div>
password again: <input name='password2' type='password'/>
</div>
<input type='submit'/>
</form>
<script>
const element = document.querySelector('.login-form')
element.addEvenListener('click', funtion(e){
alert('submit!')
})
//提交後會有提示訊息出現在網址上
```
---
### 事件傳遞機制的順序是什麼? 什麼是冒泡,什麼又是捕獲?
>[參考資料](https://ithelp.ithome.com.tw/articles/10191970) 重新認識 JavaScript: Day 14 事件機制的原理
>[參考資料](https://javascript.info/bubbling-and-capturing#this-and-event-target) Bubbling and capturing
JavaScript 是一個==事件驅動 (Event-driven)== 的程式語言,當瀏覽器載入網頁開始讀取後,雖然馬上會讀取 JavaScript 事件相關的程式碼,但是必須等到 ==「事件」被觸發(如使用者點擊、按下鍵盤等)後== ,才會再進行對應程式的執行。
###### `舉個例子`
```
當使用者點擊了按鈕,才會啟動對話框的顯示,那麼「點擊按鈕」這件事,
就被稱作「事件」(Event),而負責處理事件的程式通常
被稱為「事件處理者」(Event Handler),也就是「啟動對話框的顯示」這個動作。
```
事件流程 (Event Flow) 指的就是 ==「網頁元素接收事件的順序」== 。
事件流程可以分成兩種機制:
:::warning
事件冒泡 (Event Bubbling)
事件捕獲 (Event Capturing)
:::
``` c
<script>
addEvent('.outer')
addEvent('.inner')
addEvent('.btn')
function addEvent(className){
document.querySelector(className)
.addEventListener('click',function(){
console.log(className,'捕獲')
}, true)
document.querySelector(className)
.addEventListener('click',function(){
console.log(className,'冒泡')
}, false)
}
</script>
```
==事件冒泡==指的是「==從啟動事件的元素節點開始,逐層往上傳遞==」,直到整個網頁的根節點,也就是 document,就像是==向上回報==。
```htmlmixed=
<!DOCTYPE html>
<html>
<head>
<title>TITLE</title>
</head>
<body>
<div>CLICK</div>
</body>
</html>
```
``` c
「事件冒泡」的機制下,觸發事件的順序會是:
1. <div>CLICK</div>
2. <body>
3. <html>
4. document
```
![](https://i.imgur.com/3cUoKz9.png)
>[圖片來源](http://www.java2s.com/Book/JavaScript/DOM/Event_Flow_capture_target_and_bubbling.htm) Event Flow: capture, target, and bubbling
---
「事件冒泡」機制是由下往上來傳遞, ==「事件捕獲」(Event Capturing) 機制則正好相反==。
``` c
「事件捕獲」的機制下,觸發事件的順序會是:
1. document
2. <html>
3. <body>
4. <div>CLICK</div>
```
![](https://i.imgur.com/FAE4nda.png)
>[圖片來源](http://www.java2s.com/Book/JavaScript/DOM/Event_Flow_capture_target_and_bubbling.htm) Event Flow: capture, target, and bubbling
---
### 事件是依賴哪種機制執行的?
==兩種都會執行。==
![](https://i.imgur.com/AruGovr.png)
>[圖片來源](https://www.w3.org/TR/2003/NOTE-DOM-Level-3-Events-20031107/events.html#Events-phases) Document Object Model Events
---
### 新手 100% 會搞錯的事件機制問題
假設有兩個按鈕
如果只有幫一個元素加了`EventListener` ,==只有第一個按鈕會有反應==
``` c
<div>
<button class='btn'>1</button>
<button class='btn'>2</button>
</div>
<script>
document.querySelector('.btn')
.addEventListener('click',function(e){
alert(1)
})
</script>
//document.querySelector() 只會回傳一個元素
//只有幫一個元素加了EventListener 所以只有第一個按鈕會有反應
```
#### 如何幫每一個元素都加`EventListener`? 用`querySelectorAll `
``` c
<div>
<button class='btn' data-value='1'>1</button>
<button class='btn' data-value='2'>2</button>
<button class='btn' data-value='3'>3</button>
</div>
<script>
const elements = document.querySelectorAll('.btn')
for(var i=0;i<elements.length;i++){
elements[i].addEventListener('click',function(e){
console.log(e.target.grtAttribute('data-value'))
})
}
</script>
//querySelectorAll回傳的是一個list,雖然不是陣列但可以當成陣列來用
//用grtAttribute() 拿到屬性
```
#### 如何新增按鈕?
``` c
<div>
<div class = 'outer'>
<button class='add-btn'>add</button>
<button class='btn' data-value='1'>1</button>
<button class='btn' data-value='2'>2</button>
<button class='btn' data-value='3'>3</button>
</div>
<script>
let num = 3
document.querySelector('.add-btn').addEventListener('click',function(){
// 動態產生了新的按鈕
const btn = document.createElement('button')
btn.setAttribute('data-value',num)
btn.classList.add('btn')
btn.innerText = num
num++
document.querySelector('.outer').appendChild(btn)
})
//event delegation 事件代理,處理動態新增
document.querySelector('.outer').addEventListener('click',function(e){
if(e.target.classList.contains('btn')){
alert(e.target.getAttribute('data-value'))
}
})
</script>
```
---
#### 動態表單通訊錄
找到==點的按鈕的parent==,接著再刪掉要刪的;新增的話,新增一個row就可以了
``` c
<body>
<div class='app'>
<div>
<button class='add-btn'>新增聯絡人</button>
</div>
<div class='contacts'>
<div class='row'>
姓名:<input name='name'/>
電話:<input name='phone'/>
<button class="delete">刪除</button>
</div>
</div>
<script>
document.querySelector('.add-btn').addEventListener('click',
function(){
const div = document.createElement('div')
div.classList.add('row')
div.innerHTML=`
姓名:<input name='name'/>
電話:<input name='phone'/>
<button class="delete">刪除</button>
`
document.querySelector('.contacts').appendChild(div)
}
)
document.querySelector('.contacts').addEventListener('click',
function(e){
if (e.target.classList.contains('delete')){
document.querySelector('.contacts').removeChild(
e.target.closest('.row'))
}
}
)
</script>
</body>
```
---
### 什麼是 `event delegation`,為什麼我們需要它?
>[參考資料](https://ithelp.ithome.com.tw/articles/10192015) 重新認識 JavaScript: Day 15 隱藏在 "事件" 之中的秘密
比較有效率 ==不用浪費很多function監聽每一個事件,可以處理動態新增==。
```htmlmixed=
<ul id="myList">
<li>Item 01</li>
<li>Item 02</li>
<li>Item 03</li>
</ul>
```
分別為 myList 的 li 綁定 click 事件,就要==使用 for 迴圈來一個個綁定==:
```javascript=
// 取得容器
var myList = document.getElementById('myList');
// 分別為 li 綁定事件
if( myList.hasChildNodes() ) {
for (var i = 0; i < myList.childNodes.length; i++) {
// nodeType === 1 代表為實體 HTML 元素
if( myList.childNodes[i].nodeType === 1 ){
myList.childNodes[i].addEventListener('click', function(){
console.log(this.textContent);
}, false);
}
}
}
```
若是再新增元素至 myList:
```javascript=
// 取得容器
var myList = document.getElementById('myList');
// 分別為 li 綁定事件
if( myList.hasChildNodes() ) {
for (var i = 0; i < myList.childNodes.length; i++) {
// nodeType === 1 代表為實體 HTML 元素
if( myList.childNodes[i].nodeType === 1 ){
myList.childNodes[i].addEventListener('click', function(){
console.log(this.textContent);
}, false);
}
}
}
// 建立新的 <li> 元素
var newList = document.createElement('li');
// 建立 textNode 文字節點
var textNode = document.createTextNode("Hello world!");
// 透過 appendChild 將 textNode 加入至 newList
newList.appendChild(textNode);
// 透過 appendChild 將 newList 加入至 myList
myList.appendChild(newList);
```
==後來才新增的 newList 節點並不會有 click 事件的註冊。==
```
如果每次新增後要再重新 addEventListener 那就沒完沒了,
而且若是我們不斷地的去重覆監聽事件,又忘了移除監聽,
甚至可能會造成 memory leak 的嚴重問題。
```
==「事件指派」(Event Delegation)== 就會是比較好的做法:
```javascript=
// 取得容器
var myList = document.getElementById('myList');
// 改讓外層 myList 來監聽 click 事件
myList.addEventListener('click', function(e){
// 判斷目標元素若是 li 則執行 console.log
if( e.target.tagName.toLowerCase() === 'li' ){
console.log(e.target.textContent);
}
}, false);
// 建立新的 <li> 元素
var newList = document.createElement('li');
// 建立 textNode 文字節點
var textNode = document.createTextNode("Hello world!");
// 透過 appendChild 將 textNode 加入至 newList
newList.appendChild(textNode);
// 透過 appendChild 將 newList 加入至 myList
myList.appendChild(newList);
```
:::warning
把 click 事件改由外層的 myList 來監聽,利用事件傳遞的原理,判斷 e.target 是我們要的目標節點時,才去執行後續的動作。
:::
### `event.preventDefault()&event.stopPropagation()`
>[參考資料](https://ithelp.ithome.com.tw/articles/10192015) 重新認識 JavaScript: Day 15 隱藏在 "事件" 之中的秘密
`e.preventDefault`與`e.stopPropagation`的差別在 ==前者就只是取消預設行為==,跟事件傳遞沒有任何關係,==後者則是讓事件不再往下傳遞==。
#### `阻擋預設行為 event.preventDefault()`
常用在 a連結的href、Form表單的submit 上,有時可能只是想觸發呼叫Function,而==不想使用到原生附與的功能的話==,就可利用 `preventDefault `達成此需求。
```javascript=
var list = document.querySelector('a');
list.addEventListener('click',function(e){
e.preventDefault(); //取消預設觸發行為
/* 撰寫你的Code */
})
```
###### `範例`
```html
<a id="link" href="https://www.google.com">Google</a>
```
假設今天點擊這個 link 時,希望瀏覽器執行 `console.log('Google!')`
```javascript=
var link = document.querySelector('#link');
link.addEventListener('click', function (e) {
console.log('Google!');
}, false);
```
點擊這個 link 的時候,瀏覽器依然會帶去 google 的網頁
這時候就可以利用「事件物件」 event 提供的 `event.preventDefault() `方法:
```javascript=
var link = document.querySelector('#link');
// 在 evend handler 加上 e.preventDefault();
link.addEventListener('click', function (e) {
e.preventDefault();
console.log('Google!');
}, false);
```
再試著點擊 link 一次,會發現瀏覽器預設的跳轉頁面的行為不見了, `console.log('Google!')`; 也可順利執行。
==`event.preventDefault()` 並不會阻止事件向上傳遞 (事件冒泡)== 。
:::warning
在 `event handler function` 的最後加上 `return false`; 也會有 `event.preventDefault()` 的效果,但不可以加在前面
:::
#### 阻擋事件冒泡傳遞 `event.stopPropagation()`
有時只是想==單純針對單一元素監聽==,不想因為事件冒泡的行為,而去觸發到其他元素,這時就可利用 `stopPropagation `來達成此需求。
###### `範例`
```html
<label class="lbl">
Label <input type="checkbox" name="chkbox" id="chkbox">
</label>
```
分別為 label 與 checkbox 註冊 click 事件來實驗:
```javascript=
// label
var lbl = document.querySelector('.lbl');
// chkbox
var chkbox = document.querySelector('#chkbox');
lbl.addEventListener('click', function (e) {
console.log('lbl click');
}, false);
chkbox.addEventListener('click', function (e) {
console.log('chkbox click');
}, false);
```
`console.log('lbl click')`; 執行了兩次
```javascript=
"lbl click"
"chkbox click"
"lbl click"
```
```
因為在 label 標籤包覆 checkbox 的情況下,我們去點擊了 label 觸發 click 事件,
此時瀏覽器會自動把這個 click 事件帶給 checkbox。
checkbox 受到事件冒泡的影響,又會再度把 click 事件傳遞至 label 上,
導致 "lbl click" 出現了兩次。
```
如果要修正 label 的 click 觸發兩次的錯誤行為時,可以這樣做:
```javascript=
// label
var lbl = document.querySelector('.lbl');
// chkbox
var chkbox = document.querySelector('#chkbox');
lbl.addEventListener('click', function (e) {
console.log('lbl click');
}, false);
// 阻擋 chkbox 的事件冒泡
chkbox.addEventListener('click', function (e) {
e.stopPropagation();
}, false);
```
:::warning
stopPropagation() 不是掛在 label ,而是要放在 checkbox 上才有效!
:::
---
### 瀏覽器的資料儲存
#### `Cookie`
* Cookie就是一個==小型的文字檔==,會自動帶到server
* ==可以用javascript把資料寫到cookie==,server也可以透過HTTP的response把資料庫寫到cookie
* ==所有的request都會自動把瀏覽器的cookie帶上去==,為了讓瀏覽器可以辨識身份
#### `local storage`
>[參考資料](https://medium.com/%E9%BA%A5%E5%85%8B%E7%9A%84%E5%8D%8A%E8%B7%AF%E5%87%BA%E5%AE%B6%E7%AD%86%E8%A8%98/javascript-localstorage-%E7%9A%84%E4%BD%BF%E7%94%A8-e0da6f402453) [JavaScript] localStorage 的使用
>[參考資料](https://developer.mozilla.org/zh-TW/docs/Web/API/Window/localStorage) Window.localStorage
>[參考資料](https://kanboo.github.io/2017/12/30/JS-studynotes/) JS-基礎用法
* 伺服器跟瀏覽器的溝通
瀏覽器的全域物件提供給我們`localStorage`的用法,就像是瀏覽器提供的api, ==儲存的是key、value== ,跟物件很像!
``` c
{
key:'value'
}
// 只能存字串
```
:::info
==localstorage 只能保存 string 資料==,當資料非字串型態時怎麼辦??
解決在資料轉換過程中的問題,又或者想要讓儲存的資料不侷限在 ==「字串」的格式==,則可以==透過 `JSON.stringify() `方法==,將要儲存的資料轉換為 JSON 格式的字串;要==取出資料時,再透過 `JSON.parse()` 方法==,將資料轉換回原本的格式
```javascript=
var country = [
{farmer:'ruofan'}
];
//儲存
var countryString= JSON.stringify(country); // 轉字串
console.log(countryString);
localStorage.setItem('countryItem',countryString);
//讀取
var getData = localStorage.getItem('countryItem');
var getDataAry = JSON.parse(getData); // 轉array
console.log(getDataAry[0].farmer);
```
:::
###### `儲存`
```javascript=
localStorage.setItem('countryItem',countryString);
```
###### `讀取`
```javascript=
localStorage.getItem('countryItem');
```
伺服器也可以設定cookie
``` c
<body>
<div class='app'>
<input class="text"/><button>儲存</button>
</div>
<script>
const oldValue = window.localStorage.getItem('text')
document.querySelector('.text').value = oldvalue
document.querySelector('button').addEventListener('click',function(){
const value = document.querySelector('.text').value
window.localStorage.setItem('text',value)
})
</script>
</body>
```
#### `session storage`
* session是指一段期間,==用法跟localStorage一樣==。
* 差別在不同的分頁沒辦法共用session storage; 關了原本的分頁session storage也沒了
* ==只存在原本的分頁還開啟的狀態==,應用的範圍又更小了
* 通常拿來==存沒有想要長期保留的資訊==
:::warning
* `localStorage`可以存更長更久
* `session storage`用在短時間的資訊儲存
:::
---
### `escape-html`
>[參考資料](https://github.com/component/escape-html) component/escape-html
>[參考資料](https://gist.github.com/hraynaud/5035148) escape-html-in-js
###### `舉例 hw3-todolist`
![](https://i.imgur.com/9wSGQkh.gif)
:::warning
加上 function escapeHtml
```javascript=
// 當輸入html標籤時,會印出標籤
function escapeHtml (unsafe) {
return unsafe
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
```
:::
![](https://i.imgur.com/goQoh7N.gif)
---
>[參考資料](https://javascript.info/forms-submit) Forms: event and method submit
>[參考資料](https://eloquentjavascript.net/2nd_edition/18_forms.html) Forms and Form Fields
>[參考資料](https://www.tutorialrepublic.com/codelab.php?topic=javascript&file=form-validation) codeLad
>[參考資料](https://www.tutorialrepublic.com/javascript-tutorial/javascript-form-validation.php) JavaScript Form Validation
>[參考資料](https://www.javascripttutorial.net/javascript-dom/javascript-getattribute/) JavaScript getAttribute
>[參考資料](https://www.the-art-of-web.com/javascript/validate/) JavaScript: Form Validation
>[參考資料](https://stackoverflow.com/questions/61358620/display-message-after-form-submit-with-javascript) [stackFlow] Display message after form submit with javascript
>[參考資料](https://www.youtube.com/watch?v=rsd4FNGTRBw) JavaScript Client-side Form Validation
>[參考資料](https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation) Client-side form validation