# [FE102] 前端必備:JavaScript(筆記)
###### tags: `Lidemy學院` ` [FE102] 前端必備:JavaScript`
# 課程簡介
## 課程簡介
基本上在網頁上頁JavaScript時,所關注的面向,有3個 :
1. 介面 : 如何改變
如何使用 JavaScript 去改變,用 html 跟 css ,所寫出來的 UI,例如改變元素,新增元素。
2. 事件 : 如何監聽事件並做出反應
比如,按一個按紐去新增元素,或是按按鈕改變背景顏色,如果會介面跟事件,能做出7-8成的東西。
3. 資料 : 如何跟伺服器交換資料
或是如何讓資料再瀏覽器保存下來。
> 基本上如果會以上3點,能寫出任何的程式,網頁的部分,不外乎就這3點。
___
# JavaScript 與瀏覽器的溝通
## 執行 JavaScript 的一百種方式
在執行 JavaScript 前,要思考 JavaScript 能在哪裡執行,重點是==在哪裡執行 ?==
第一種方式 : `<script>`放在任何地方都可以,但通常放在`</body>`前,
```
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FE102</title>
<script>
alert('hello')
</script>
</head>
<body></body>
</html>
```
第二種方式 : 將 JS 檔,寫成一個檔案,在引入html檔中
```
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FE102</title>
<script src="./index.js"></script>
</head>
<body></body>
</html>
```
```
//index.js
alert('hello')
```
探討 : 在 Node.js 與 瀏覽器跑 JS 的差別 ?
1. 在跑 JS 時,都是一樣的,在瀏覽器中, Node.js 有些語法並不支援,Node.js 與 瀏覽器跑,是兩個不同的執行環境,所以可能這個支援,那個不支援,所以有些用法在 Node.js 中是可以的,在瀏覽器中不行的,反過來也是一樣
___
## DOM 是什麼?
[文件物件模型 (DOM)](https://developer.mozilla.org/zh-TW/docs/Web/API/Document_Object_Model),(Document Object Model, DOM),簡單的解釋是將 Document 轉換成 Object,DOM 是指在 html 的那些內容,可以想像把 html 文件的這結構,變成很多節點,並有階層關係,可以把標籤轉換成下圖,轉換成下圖其實感覺就跟物件差不多,所以 JS 怎麼改變介面,就是透過 DOM ,會透過 JS 去拿到 DOM ,意思就是可以拿到這個節點(元素),並去做改變,儘管你寫的是 JS ,瀏覽器會幫助你去改變瀏覽器上的東西。
==DOM 是什麼? 瀏覽器提供這個橋梁(DOM) ,讓 JS 能改變畫面的東西。==

___
## 如何選到想要的元素:getElement
`document` 是瀏覽器提供的物件,有許多function,可以使用。
HTMLCollection : 用起來像是陣列,但並不是陣列,算是特殊形態的陣列,使用起來並沒有甚麼差。
NodeList : 跟 HTMLCollection 意思差不多,像是陣列,但並不是陣列。
在使用上,`querySelector`跟`querySelectorAll`,較常使用。
1. `getElementsByTagName` : 取標籤
```
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FE102</title>
</head>
<body>
<div>
hello~~
</div>
<div>
yoyoyo~~
</div>
<script>
const elements = document.getElementsByTagName('div')
console.log(elements)
console.log(elements[0])
</script>
</body>
</html>
```
2. `getElementsByClassName` : 取Class
```
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FE102</title>
</head>
<body>
<div class="block">
hello~~
</div>
<div class="block">
yoyoyo~~
</div>
<script>
const elements = document.getElementsByClassName('block')
console.log(elements)
console.log(elements[0])
</script>
</body>
</html>
```
3. `getElementById` : 取Id,在使用上,會直接找到元素,Id在使用上是唯一的。
```
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FE102</title>
</head>
<body>
<div id="block">
hello~~
</div>
<div>
yoyoyo~~
</div>
<script>
const elements = document.getElementById('block')
console.log(elements)
</script>
</body>
</html>
```
4. `querySelector` : 取css的選擇器,只會回傳第一個取到的元素
```
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FE102</title>
</head>
<body>
<div id="block">
hello~~
</div>
<div>
yoyoyo~~
</div>
<script>
const elements = document.querySelector('div')
console.log(elements)
</script>
</body>
</html>
```
5. `querySelectorAll` : 取css的選擇器,會回傳所有符合的元素
```
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FE102</title>
</head>
<body>
<div id="block">
hello~~
</div>
<div>
yoyoyo~~
</div>
<script>
const elements = document.querySelectorAll('div')
console.log(elements)
</script>
</body>
</html>
```
___
## 改變元素的 CSS
1. 所選元素`.style`.屬性 = '值' : 可以去改變元素,通常不太會如此使用,會先寫好class,在去改變。
```
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FE102</title>
</head>
<body>
<div id="block">
hello~~
</div>
<script>
const element = document.querySelector('#block')
element.style.background = 'red'
element.style.padding = '10px'
element.style['padding-top'] = '30px'
element.style.paddingBottom = '40px'
</script>
</body>
</html>
```
___
## 改變元素的 Class
在改變元素中通常是,改變元素的 Class,在用 Class 去負責不同的 style 。
1. 新增class : 所選元素.classList.add('className')
2. 移除class : 所選元素.classList.remove('className')
3. 開關class,有此class,會移除,無此class,會新增 : 所選元素.classList.toggle('className'),適合使用在許多地方
```
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FE102</title>
<style>
.action {
background-color: red;
}
</style>
</head>
<body>
<div id="block" class="main box1">
hello~~
</div>
<script>
const element = document.querySelector('#block')
element.classList.add('action')
element.classList.remove('main')
element.classList.toggle('box')
element.classList.toggle('box1')
</script>
</body>
</html>ement.classList.remove('main')
</script>
</body>
</html>
```
___
## 改變內容:inner、outer 的 HTML 與 text
1. `innerText` : 改變內容文字,只會抓到文字
2. `innerHTML` : 將標籤中的內容都抓出來
3. `outerHTML` : 將標籤自己與其內容都抓出來
常用 : `innerText`,改變內容文字。
```
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FE102</title>
</head>
<body>
<div id="block">
yoyo~~
<a>hello~~</a>
</div>
<script>
const element = document.querySelector('#block > a')
console.log(element.innerText)
element.innerText = '!!!'
console.log(element.innerHTML)
console.log(element.outerHTML)
element.outerHTML = '<h1>h1</h1>'
</script>
</body>
</html>
```
___
## 插入與刪除元素:appendChild 與 removeChild
在操作插入與刪除元素時,需要知道父層是誰。
1. 刪除元素 : `removeChild`
2. 插入元素 : `appendChild`
3. 新增元素 : `createElement`
4. 新增文字 : `createTextNode`
```
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FE102</title>
</head>
<body>
<div id="block">
yoyo~~
<a>hello~~</a>
</div>
<script>
const element = document.querySelector('#block')
element.removeChild(document.querySelector('a'))
const item = document.createElement('div')
item.innerText = '123'
element.appendChild(item)
const item2 = document.createTextNode('Text')
element.appendChild(item2)
</script>
</body>
</html>
```
___
# JavaScript 網頁事件處理
## eventListener 與 callback function
### event Listener (事件監聽)
在這 element 上,新增(add)一個 event Listener (事件監聽),叫做 click ,重點是按完後要做甚麼事情,就是 onClick 這 function,所以當 click 事件發生時,瀏覽器會去觸發 onClick 這 function ,這 function 叫做 callback function (回呼函式) ,所以在這 function 中,可以做任何想做的事,通常都是以比較偷懶的方式去寫,addEventListener( ),第 2 參數,要放 function ,所以可以直接寫 function 在裡面,這時這 function 是沒有名稱的,所以叫做匿名函式,它跟 callback function 一點關係都沒有,叫匿名函式只是沒有名字而已,任何沒有名字 function ,都叫匿名函式,有許多事件可以做監聽,需要可以去 google。
```
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FE102</title>
</head>
<body>
<div id="block">
yoyo~~
<a>hello~~</a>
</div>
<script>
const element = document.querySelector('#block')
element.addEventListener('click', onClick)
function onClick() {
alert('click!')
}
</script>
</body>
</html>
```
```
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FE102</title>
</head>
<body>
<div id="block">
yoyo~~
<a>hello~~</a>
</div>
<script>
const element = document.querySelector('#block')
element.addEventListener('click', function () {
alert('click!')
})
</script>
</body>
</html>
```
___
## 詳細講解 callback function
### callback function :
當有觸發事情,才會去做事的 function 大約可以叫做 callback function,傳入的參數是函式,就可以叫做 callback function。
callback function 的意思其實就是:「當某事發生的時候,請利用這個 function 通知我」
在使用 callback function 時,有一個初學者很常犯的錯誤一定要特別注意。都說了傳進去的參數是 callback function,是一個「function」,不是 function 執行後的結果(除非你的 function 執行完會回傳 function,這就另當別論)。
[JavaScript 中的同步與非同步(上):先成為 callback 大師吧!](https://blog.techbridge.cc/2019/10/05/javascript-async-sync-and-callback/)
___
## event(e) 是什麼碗糕?
在上述範例中,其實在 eventListener 中,可以拿到許多資訊,瀏覽器在呼叫你的 function 時,會傳遞進來一個參數,通常會叫 event ,或最多的簡寫是 e ,名稱是可以隨便取的,只是一個參數而已。
開啟瀏覽器去點擊, 可以開 console.log ,出來看到點了哪裡,這 e 是一個 MouseEvent,可以知道看到 x 跟 y ,比較重要 的是 tarage 是指點到的元素,
```
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FE102</title>
</head>
<body>
<div id="block">
yoyo~~
<a>hello~~</a>
</div>
<script>
const element = document.querySelector('#block')
element.addEventListener('click', function (e) {
console.log(e)
})
</script>
</body>
</html>
```

可以用 e.tarage,知道點到的元素
```
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FE102</title>
</head>
<body>
<div id="block">
yoyo~~
<a>hello~~</a>
</div>
<script>
const element = document.querySelector('#block')
element.addEventListener('click', function (e) {
console.log(e.target)
})
</script>
</body>
</html>
```

所以在 e 中,可以取到許多資訊,e 是瀏覽器給的資訊,可以利用此資訊,取的跟事件相關的東西。
以下為 input 做示範 ,keydown:
```
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FE102</title>
</head>
<body>
<div id="block">
yoyo~~
<input type="text" name="" id="" />
<a>hello~~</a>
</div>
<script>
const element = document.querySelector('input')
element.addEventListener('keydown', function (e) {
console.log(e.key)
})
</script>
</body>
</html>
```

示範 : 按按鈕切換背景顏色的功能
```
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FE102</title>
<style>
.active {
background: red;
}
</style>
</head>
<body>
<div id="block">
yoyo~~
<input type="text" name="" id="" />
<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>
</html>
```
___
## 表單事件處理 onSubmit
範例 :
```
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FE102</title>
<style>
.active {
background: red;
}
</style>
</head>
<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.addEventListener('submit', function (e) {
alert('submit!!')
})
</script>
</body>
</html>
```
preventDefault() : 瀏覽器提供的 function ,可以取消事件預設行為 ,Submit 預設行為就是送出表單。
可以使用在哪裡,判斷 password 跟 password again ,是否相同 :
```
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FE102</title>
<style>
.active {
background: red;
}
</style>
</head>
<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.addEventListener('submit', function (e) {
const input1 = document.querySelector('input[name = password]')
const input2 = document.querySelector('input[name = password2]')
if (input1.value !== input2.value) {
alert('密碼不同')
e.preventDefault()
}
})
</script>
</body>
</html>
```
___
## 阻止預設行為:preventDefault
e.preventDefault : 阻止瀏覽器預設行為,其實除了表單送出外,還可以用在其他地方,以下舉例 :
[[筆記][JavaScript]所謂的「停止事件」到底是怎麼一回事?](https://ithelp.ithome.com.tw/articles/10198999)
`<a>` 超連結 : 取消連到某個地方
```
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FE102</title>
<style>
.active {
background: red;
}
</style>
</head>
<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" />
<a href="/test">link</a>
</form>
<script>
const element = document.querySelector('.login-form')
element.addEventListener('click', function (e) {
e.preventDefault()
})
</script>
</body>
</html>
```
`<input>` 輸入框 : 無法送出 e ,以下為範例
```
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FE102</title>
<style>
.active {
background: red;
}
</style>
</head>
<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" />
<a href="/test">link</a>
</form>
<script>
const element = document.querySelector('input[name = username]')
element.addEventListener('keypress', function (e) {
if (e.key === 'e') {
e.preventDefault()
} else {
console.log(e.key)
}
})
</script>
</body>
</html>
```

___
## 比複雜更複雜的事件傳遞機制
瀏覽器 事件傳遞機制 : 點擊區塊的影響,是有順序的,會先從近的開始。以下 console.log(),是在 冒泡階段
[DOM 的事件傳遞機制:捕獲與冒泡](https://blog.techbridge.cc/2017/07/15/javascript-event-propagation/)
```
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FE102</title>
<style>
.outer {
width: 500px;
height: 200px;
background: red;
}
.inner {
width: 300px;
height: 100px;
background: green;
}
</style>
</head>
<body>
<div class="outer">
<div class="inner">
<button class="btn">click me</button>
</div>
</div>
<script>
addEvent('.outer')
addEvent('.inner')
addEvent('.btn')
function addEvent(className) {
document
.querySelector(className)
.addEventListener('click', function () {
console.log(className)
})
}
</script>
</body>
</html>
```

___
## 事件傳遞機制詳解:捕獲與冒泡
以下圖去理解,事件傳遞機制。
問題1 : 如何將 event Listener (事件監聽) ,掛在不同階度。
`addEventListener( )`,有第3參數,可以放 bool
* false : 放在冒泡階段上。
* true : 放在捕獲階段上。
在 target Phase 上比較特別,會先看 哪個 event Listener 在前,就執行哪個。

以下為範例 : 點擊 click me ,開啟 console
```
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FE102</title>
<style>
.outer {
width: 500px;
height: 200px;
background: red;
}
.inner {
width: 300px;
height: 100px;
background: green;
}
</style>
</head>
<body>
<div class="outer">
<div class="inner">
<button class="btn">click me</button>
</div>
</div>
<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>
</body>
</html>
```

___
## 別向上級回報:stopPropagation
舉例 : 冒泡階段 就像是 向上級回報一樣,那如果不想向上回報時,只想自己可以接收到事件就好,不想到冒泡階段,讓事件傳遞下去。
* stopPropagation()
* stopImmediatePropagation()
它們的共同點:
都是阻止後續的偵聽行為,即能阻擋掉事件流中事件的冒泡,簡而言之就是讓後面的偵聽都不執行;
不同點:
是擁有事件監聽函數的當前的節點是否執行該函數,stopPropagation()方法阻止事件對象移到到另一個節點上,但是允許當前節點的其他事件監聽函數執行,而stopImmediatePropagation()方法不僅阻止事件從當前節點移動到另一個節點上,它還不允許當前節點的其他事件監聽函數執行。
舉例 : 如下,可以在 botton ,下不只一個監聽,如使用 e.stopPropagation(),多個監聽都會有效過,如下 e.stopImmediatePropagation(),只會監聽一個事件,其他都沒效果。
```
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FE102</title>
<style>
.outer {
width: 500px;
height: 200px;
background: red;
}
.inner {
width: 300px;
height: 100px;
background: green;
}
</style>
</head>
<body>
<div class="outer">
<div class="inner">
<button class="btn">click me</button>
</div>
</div>
<script>
addEvent('.outer')
addEvent('.inner')
function addEvent(className) {
document
.querySelector(className)
.addEventListener('click', function () {
console.log(className, '冒泡')
})
}
document.querySelector('.btn').addEventListener('click', function (e) {
e.stopPropagation()
console.log('btn 冒泡')
})
</script>
</body>
</html>
```

___
## 新手 100% 會搞錯的事件機制問題
程式是一行一行執行,在用 querySelectorAll 時,這個 elements 是只有那兩個按鈕 ,動態新增的按鈕,並沒有 事件機制。
data-value : 存放資訊 。
動態新增的元素,不會加入先寫好的事件監聽。
```
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FE102</title>
<style></style>
</head>
<body>
<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>
</div>
<script>
let num = 3
const elements = document.querySelectorAll('.btn')
for (var i = 0; i < elements.length; i++) {
elements[i].addEventListener('click', function (e) {
alert(e.target.getAttribute('data-value'))
})
}
document.querySelector('.add-btn').addEventListener('click',
function () {
const btn = document.createElement('button')
btn.setAttribute('data-value', num)
btn.innerText = num
num++
document.querySelector('.outer').appendChild(btn)
}
)
</script>
</body>
</html>
```
___
## 欸等等幫我拿餐點:event delegation
event delegation (事件代理),請回憶起 事件傳遞機制詳解:捕獲與冒泡
* 常用,比較有效率,節省許多資源,不用加那麼多 function ,浪費那麼多 function,去監聽每一個事件,因為每一個是事件做的事情都差不多,所以可以只使用一個 EventListener 去做管理就好
* 可以處理,動態新增的情形,動態還是可以抓到,特過冒泡機制,傳遞上去,所以即使底下新增的元素,還是可以接到他的事件。
在範例中,將在 `btn` 的事件監聽,加在 `.outer` 上,稱事件代理。
* `contains('n')` : 判斷,`classList`,是否有 n 這 class。
```
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FE102</title>
<style></style>
</head>
<body>
<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>
</div>
<script>
let num = 3
const elements = document.querySelectorAll('.btn')
for (var i = 0; i < elements.length; i++) {
elements[i].addEventListener('click', function (e) {
alert(e.target.getAttribute('data-value'))
})
}
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)
})
document.querySelector('.outer').addEventListener('click', function (e) {
if (e.target.classList.contains('btn')) {
alert(e.target.getAttribute('data-value'))
}
})
</script>
</body>
</html>
```
___
## 綜合示範:簡易密碼產生器
備註:
影片裡的程式碼其實有一個 bug,那就是儘管你沒有勾選英文字母,還是會產生出來,那是因為 getChar 這個 function 最後忘記加上 return ''
所以預設會 return undefined,被轉成文字之後產生出的結果就會有 undefined 或是部分字母,造成程式的 bug
getChar 的最後加上 return '' 就行了
範例 :
```
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FE102</title>
<style>
body {
font-size: 40px;
}
.result {
background: rgba(0, 0, 0, 0.5);
}
</style>
</head>
<body>
<div class="app">
<div>
<label for="">
<input type="checkbox" name="en" />
英文
</label>
</div>
<div>
<label for="">
<input type="checkbox" name="num" />
數字
</label>
</div>
<div>
<label for="">
<input type="checkbox" name="sp" />
特殊符號
</label>
</div>
<div>
<button class="btn-generate">產生</button>
<div class="result"></div>
</div>
</div>
<script>
document
.querySelector('.btn-generate')
.addEventListener('click', function () {
let availableChar = ''
if (document.querySelector('input[name = en]').checked) {
availableChar += 'abcdefghijklmnopqrstuvwxyz'
}
if (document.querySelector('input[name = num]').checked) {
availableChar += '0123456789'
}
if (document.querySelector('input[name = sp]').checked) {
availableChar += '!@#$%^&*()'
}
let result = ''
for (let i = 0; i < 10; i++) {
//0 ~ availableChar.length - 1
const number = Math.floor(Math.random() * availableChar.length)
result += availableChar[number]
}
document.querySelector('.result').innerText = result
})
</script>
</body>
</html>
```
優化 :
```
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FE102</title>
<style>
body {
font-size: 40px;
}
.result {
background: rgba(0, 0, 0, 0.5);
}
</style>
</head>
<body>
<div class="app">
<div>
<label for="">
<input type="checkbox" name="en" />
英文
</label>
</div>
<div>
<label for="">
<input type="checkbox" name="num" />
數字
</label>
</div>
<div>
<label for="">
<input type="checkbox" name="sp" />
特殊符號
</label>
</div>
<div>
<button class="btn-generate">產生</button>
<div class="result"></div>
</div>
</div>
<script>
function getChar(name, char) {
if (document.querySelector('input[name = ' + name + ']').checked) {
return char
}
return
}
document
.querySelector('.btn-generate')
.addEventListener('click', function () {
let availableChar = ''
availableChar += getChar('en', 'abcdefghijklmnopqrstuvwxyz')
availableChar += getChar('num', '0123456789')
availableChar += getChar('sp', '!@#$%^&*()')
let result = ''
for (let i = 0; i < 10; i++) {
//0 ~ availableChar.length - 1
const number = Math.floor(Math.random() * availableChar.length)
result += availableChar[number]
}
document.querySelector('.result').innerText = result
})
</script>
</body>
</html>
```
再優化 :
```
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FE102</title>
<style>
body {
font-size: 40px;
}
.result {
background: rgba(0, 0, 0, 0.5);
}
</style>
</head>
<body>
<div class="app">
<div>
<label>
<input
type="checkbox"
name="en"
data-char="abcdefghijklmnopqrstuvwxyz"
/>
英文
</label>
</div>
<div>
<label>
<input type="checkbox" name="num" data-char="0123456789" />
數字
</label>
</div>
<div>
<label>
<input type="checkbox" name="sp" data-char="!@#$%^*()" />
特殊符號
</label>
</div>
<div>
<button class="btn-generate">產生</button>
<div class="result"></div>
</div>
</div>
<script>
document
.querySelector('.btn-generate')
.addEventListener('click', function () {
let availableChar = ''
const elements = document.querySelectorAll('input[type=checkbox]')
for (let i = 0; i < elements.length; i++) {
if (elements[i].checked) {
availableChar += elements[i].getAttribute('data-char')
}
}
let result = ''
for (let i = 0; i < 10; i++) {
//0 ~ availableChar.length - 1
const number = Math.floor(Math.random() * availableChar.length)
result += availableChar[number]
}
document.querySelector('.result').innerText = result
})
</script>
</body>
</html>
```
___
## 綜合示範:動態表單通訊錄
```
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FE102</title>
<style>
body {
font-size: 40px;
}
.result {
background: rgba(0, 0, 0, 0.5);
}
</style>
</head>
<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>
</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>
</html>
```
___
# 如何在瀏覽器上儲存資料?
## 網頁的資料都存在哪裡?為什麼換台電腦購物車就清空了?
瀏覽器的資料儲存,我們可以利用js把資料做儲存,把想存的資料存在瀏覽器中,所以我們可以存資料的地方不只一個,瀏覽器會有一個地方拿來存你的資料,因為一個最基本的問題,http的狀態是如何,也就是說它不知道,上一個人跟現在是不是同一個人,所以我們需要一些方法,將資料自動帶到server去,讓他可以認識我們,所以這也是,為甚麼有些購物網站,我把一些商品加入購物車,換了個b電腦,購物車商品就沒有了,代表商品是存在a電腦上,不是存在後端,如果是存在後端的資料庫,不管事用哪一台電腦,都是抓地到的,如果換了一台電腦購物車就清空,代表是將資訊純在瀏覽器裡的
___
## 最古老的方式:Cookie
[Cookie - wiki](https://zh.wikipedia.org/wiki/Cookie) : 其實是個小型文字檔,會自動帶到 server。
我們可以用js將資料寫在 Cookie , server 也可以透過,http 的 response ,把資料寫進 Cookie, server 有一個 response ,叫做 Set-Cookie : ...,裡面會放一些資訊,只要瀏覽器看到 Set-Cookie,就會將 Cookie 給寫入,所以的 request 都會把 瀏覽器的 Cookie 給帶上去,為了讓 瀏覽器 辨識身分 ,辨識身分指的意思,有時候會被用在 廣告追蹤 ,可以打開隨便一個網站,開啟 檢查,的 Application,Storage 裡 就有 Cookie ,裡面毀有一堆 domain ,裡面有些 id 就是拿來追蹤你用的,當你拜訪網站時,這網站插入了些 google 程式碼,那 google 就知道,你來哪一些網站,google 就能用這去辨認 剛剛跟現在是否是同一個人。
所以 Cookie ,是會用在 身分驗證上。
舉例 : 登入,如何判斷是同一個人,第一次拜訪某網站時,server 發通行證會放在 Cookie,第2次拜訪時,瀏覽器 都會資自動帶上 Cookie,那 server 就知道是同一個人。
Cookie 會放很多 domain,瀏覽器會帶跟你在拜訪的網站相關 Cookie,給 你在拜訪的 server。
___
## 最推薦的儲存方式:local storage
Cookie 像是伺服器跟瀏覽器的一個溝通。實際上伺服器,也可以設定 Cookie,所以一般來說,我們想要 儲存資訊,跟 server沒關時,其實會用另一個叫 local storage。
範例 : 按按鈕,將資訊存入 local storage,開啟 檢查,的 Application,local storage 裡可以看到資訊,只能存字串。
```
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FE102</title>
<style></style>
</head>
<body>
<div class="app">
<input type="text" class="text" />
<button class="add-btn">储存</button>
</div>
<div class="app1"></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>
</html>
```
___
## 一閃即逝:session storage
session storage 跟 Cookie 用法是一樣的,session 是一種有時間概念的意思在。
特點是拿來存些短期的資訊,如換分頁, session storage 的資訊會消失。
範例 : 將上方程式,改成 session storage,其他都不用變。開新分頁,資訊就不見了,只能短期方資料。
```
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FE102</title>
<style></style>
</head>
<body>
<div class="app">
<input type="text" class="text" />
<button class="add-btn">储存</button>
</div>
<div class="app1"></div>
<script>
const oldValue = window.sessionStorage.getItem('text')
document.querySelector('.text').value = oldValue
document.querySelector('button').addEventListener('click', function () {
const value = document.querySelector('.text').value
window.sessionStorage.setItem('text', value)
})
</script>
</body>
</html>
```
___
# 中場總結
## 學了這些之後,可以幹嘛?
發揮想像力,學會以上技術基本上只要不跟網路相關,都能做出來,現在可以監聽是監,可以改變UI,所以這兩件事,就是網頁的元素,像上學會這兩項,可以寫出些遊戲,只是欠缺些網路資料交換的能力,其他都寫得出來,貪吃蛇,表單驗證等等...
___
## 遺漏的最後一塊拼圖:資料的交換
已學會,如何用JS改變UI,如何用JS去監聽事件,然後做出反應,還沒學到的事,怎麼跟 server 做資料交換,例如送資料給 server , server 在返回資料給你,JS關注的要點都會了,只要會這3點,所有看的網頁跟JS有關的都做得出來。
___
# 網頁與伺服器的溝通
## 幫你喚回記憶:API 與網頁伺服器
Client 發送 request 到 Server,Server 回傳 response ,response 有可能是 .html 檔案或是 JSON 格式檔案。
瀏覽器上輸入網址(Client 發送 request),顯示的畫面(Server 回傳 response(.html 檔案))。
___
## 用 node.js 呼叫 API 與在網頁上呼叫的根本差異是什麼?
差別 :
使用 node.js 發送 request ,到 Server ,這中間過程是沒有人限制你的。
使用瀏覽器上的JS 發送 request,到 Server ,中間會被瀏覽器限制,第一個是瀏覽器可能會阻止你做一些事情,第二個是瀏覽器會幫我們加一些東西,例如,你的瀏覽器的版本是甚麼,會加些而外的資訊,Server 收到的 request ,就會有而外的資訊,那再用 node.js 時,你不加的資訊,就不會發送到 Server。


___
## 傳送資料的第一種方式:表單 form
範例 : 使用 form 傳送資料 跟 JS 沒關係
```
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>form</title>
</head>
<body>
<form method="GET" action="https://google.com">
username:
<input name="username" />
<input type="submit" />
</form>
</body>
</html>
```
再用 GET,發送時,資訊會帶到網址後面,POST,發送時,資訊會帶到body裡。
發送出去後,瀏覽器收到後,會直接換頁(回傳的 response 是 .html 檔案 )。
==下圖,輸入123,提交,301轉址。==

==下圖,出現google頁面,因為回傳的response,是google的html檔==

==下圖,response回傳回來的,google的html檔==

___
## 傳送資料的第二種方式:ajax
第一種方式的缺點,會換頁。
第二種方式:可以不換頁,在頁面上,去改變部分內容。
ajax(Asynchronous JavaScript and XML),只要是任何伺服器交換資料,是用 JavaScript,都可以叫 ajax。
原理:
瀏覽器的 JS 透過瀏覽器發送request給Server回應response給瀏覽器,瀏覽器將結果給瀏覽器的 JS。
瀏覽器的 JS -> 瀏覽器(request) -> Server -> 瀏覽器(response) -> 瀏覽器的 JS

```
解釋 :
// XMLHttpRequest(),瀏覽器提供的物件,new可以去實作。
const request = new XMLHttpRequest()
//request.onload,放一個function,request現在是一個物件,實作後,會執行onload,這function。
//if,判斷是狀態是否成功,responseText,取得的內容。err,錯誤訊息。
request.onload = function () {
if (request.status >= 200 && request.status <= 400) {
console.log(request.responseText)
} else {
console.log(err)
}
}
//如果有錯誤,會觸發的 function
request.onerror = function () {
console.log('error')
}
//open,要發request的地方,可以填3個參數,method,url,bool。
//bool,true,非同步。false,同步。ajax一定是非同步。
request.open('GET', '', true)
//真的將request,發出。
request.send()
```
範例 : 可以開啟,檢查,Network,Response,查看資料。
```
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ajax - text</title>
</head>
<body>
<div class="app"></div>
</body>
<script>
const request = new XMLHttpRequest()
request.onload = function () {
if (request.status >= 200 && request.status <= 400) {
console.log(request.responseText)
} else {
console.log(err)
}
}
request.onerror = function () {
console.log('error')
}
request.open('GET', 'https://reqres.in/api/users', true)
request.send()
</script>
</html>
```
___
## 詳細解析 XMLHttpRequest
重點 : 回傳回來的是字串,需要用[JSON.parse()](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse),去轉成物件使用。
在解釋一次,ajax,的實作範例。可以將上章範例,的onload,用EventListene去做。
```
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ajax - text</title>
</head>
<body>
<div class="app"></div>
</body>
<script>
const request = new XMLHttpRequest()
request.addEventListener('load', function () {
if (request.status >= 200 && request.status <= 400) {
console.log(request.responseText)
} else {
console.log(request.status)
}
})
request.onerror = function () {
console.log('error')
}
request.open('GET', 'https://reqres.in/api/users', true)
request.send()
</script>
</html>
```
___
## Same origin policy 與跨網域問題
好文推薦:https://blog.techbridge.cc/2017/05/20/api-ajax-cors-and-jsonp/
1.瀏覽器的政策 : Same origin policy(同源政策),甚麼是同源,可以看成相同網域,就是同源,HTTP跟HTTPS,是分開的。
2.瀏覽器的政策 : 跨來源資源公用 ( CORS ),這東西就是為了解決,同源政策的問題,有時可能會去別人的Server,拿資料,所以需要有此功能,這時就有一個規範。
如果想要跨來源存取在header,加 Access-Control-Allow-Origin,哪些來源可以存取API的response,這來源是甚麼,在發 request,瀏覽器會在 request 的 header 裡,加上 Origin,就是你現在網域的 Domain,瀏覽器會自動加上來源,在server端,就可以決定是否給他存取的權限。
所以要解決這問題,除非是同來源,在同樣的 Domain,或是 server 加上 Access-Control-Allow-Origin。
___
## 瀏覽器住海邊嗎,為什麼管這麼寬?
為甚麼要有同源政策,跨來源資源公用,這兩個政策,所以到底要怎麼存取,跨網域的資源。
因為安全性,瀏覽器許多政策,都是因為安全性,為了不讓他人可以隨意存取他人電腦的資源。
以上瀏覽器政策,都是瀏覽器加的枷鎖,如果沒有瀏覽器,像是用 node,去發 request,並沒有那些枷鎖。
___
## 你絕對用過,但絕對沒想過的第三種方式
補充講解,現在已經很少再用了。[JSONP (JSON with Padding)](https://www.fooish.com/json/jsonp.html)
利用有些標籤不會受到同源政策的影響,例如`<img>`、`<script>`,使用`<script>`去拿到資料。
___
## 單向傳送資料的延伸應用(email 與追蹤Domain)
單向傳送資料,範例,如何知道發送的 email,被打開的次數追蹤,廣告信的次數追蹤。
例如,在 email中,放一個透明的`<img width="1" heigth="1" src="https://example.com/users_open/123">`圖,當使用者開信,就會發送一個 request,去計算次數。
___
## 綜合示範:抓取資料並顯示
1.先將版型刻好。
```
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ajax - text</title>
<style>
body {
font-size: 38px;
}
.profile {
border: 1px solid #333;
margin-top: 20px;
display: inline-flex;
align-items: center;
}
.profile__name {
margin: 0 10px;
}
</style>
</head>
<body>
<div class="app">
<div class="profile">
<div class="profile__name">George Bluth</div>
<img
class="profile__img"
src="https://reqres.in/img/faces/1-image.jpg"
/>
</div>
</div>
</body>
<script>
const request = new XMLHttpRequest()
request.addEventListener('load', function () {
if (request.status >= 200 && request.status <= 400) {
console.log(request.responseText)
} else {
console.log(request.status)
}
})
request.onerror = function () {
console.log('error')
}
request.open('GET', 'https://reqres.in/api/users', true)
request.send()
</script>
</html>
```
2.拿到資料
3.將固定的內容,換成是動態的內容。
```
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ajax - text</title>
<style>
body {
font-size: 38px;
}
.profile {
border: 1px solid #333;
margin-top: 20px;
display: inline-flex;
align-items: center;
}
.profile__name {
margin: 0 10px;
}
</style>
</head>
<body>
<div class="app"></div>
</body>
<script>
const request = new XMLHttpRequest()
const container = document.querySelector('.app')
request.addEventListener('load', function () {
if (request.status >= 200 && request.status <= 400) {
const response = request.responseText
const json = JSON.parse(response)
const users = json.data
for (let i = 0; i < users.length; i++) {
const div = document.createElement('div')
div.classList.add('profile')
div.innerHTML = `<div class="profile__name">${users[i].first_name} ${users[i].last_name}</div>
<img class="profile__img" src="${users[i].avatar}"/>
`
container.appendChild(div)
}
} else {
console.log(request.status)
}
})
request.onerror = function () {
console.log('error')
}
request.open('GET', 'https://reqres.in/api/users', true)
request.send()
</script>
</html>
```
___
## 小地方大學問:內容產生的地方在 client 還是 server
client side rendering : 用 js 動態去產生內容。
以上範例,去檢視網頁原始碼,看起來是沒有內容的。
缺點是,搜尋引擎,看到此網頁會認為,是沒有內容的,因為你的內容都是 js 動態產生的。
___
# 總結
## 課程總結
基本上在網頁上頁JavaScript時,所關注的面向,有3個 :
1. 介面 : 如何改變
如何使用 JavaScript 去改變,用 html 跟 css ,所寫出來的 UI,例如改變元素,新增元素。
2. 事件 : 如何監聽事件並做出反應
比如,按一個按紐去新增元素,或是按按鈕改變背景顏色,如果會介面跟事件,能做出7-8成的東西。
3. 資料 : 如何跟伺服器交換資料
或是如何讓資料再瀏覽器保存下來。
> 基本上如果會以上3點,能寫出任何的程式,網頁的部分,不外乎就這3點。
___