# [FE102] 前端必備:JavaScript
###### Date : 2021 May. 24 - 25
#### JavaScript 與瀏覽器的溝通
- Node.js 與 瀏覽器是不一樣的 ( Runtime 不一樣 )
- DOM ( Document Object Moduel )
> 將 Document 轉成 Object
- 選到想要的元素
- getElementsByTagName
- getElementsByClassName
- getElementById ( 只會有一個 )
- querySelecter ( 同 CSS 選擇器,但只會回傳選到第一個 )
- queryAllSelecter ( 同 CSS 選擇器,回傳全選很像陣列的陣列 )
- 改變 CSS
- element.style.background = 'red'
- element.style['padding-top'] = '10px' ( object like )
- element.style.paddingTop = '10px' ( JS camelCase )
- 改變 Class
- element.classList.add('active')
- element.classList.remove('active')
- element.classList.toggle('active') ( 開 / 關 )
- 改變內容
- element.innerText = 'hello'
- element.innerHTML ( 內部所以的內容包含 html 標籤 )
- element.outterHTML ( 包含本身的 html 標籤,少用 )
- 刪除、插入元素
- element.removeChild(docment.querySelector('a'))
- 插入元素,如下 :
```javascript
// 插入 div
const element = document.querySelector('#block')
const item = document.cteateElement('div')
item.innerText = '123'
element.appendChild(item)
// 插入存文字
const element = document.querySelector('#block')
const item = document.cteateTextNode('hello')
element.appendChild(item)
```
#### JavaScript 網頁事件處理
- eventListener
- element.addEventListener('click', onClick)
- callback function
- 當有事件觸發的時候,呼叫 function
- event(e)
- element.addEventListener('click', function(e.target))
```javascript
const element = doxument.querySelector('#block')
element.addEventListener('click', onClick)
function onClick(e) {
console.log(e)
}
```
- keydown 事件 :
```javascript
const element = document.querySelector('input')
element.addEventListener('keydown', function(e){
console.log(e.key)
})
```
- onSubmit & preventDefault()
- 送出 form 表單,預設 methed 是 get
```javascript
const element = document.querySelector('.login-form')
element.addEventListener('submit', function(e){
const input1 = document.querySelector('input[name=password1]')
const input2 = document.querySelector('input[name=password2]')
if (input1.value !== input2.value) {
alert('密碼不同')
e.preventDefault()
// 可以預防瀏覽器預設動作,ex. 送出動作、超連結
}
})
```
- 事件傳遞機制 : 捕獲 & 冒泡
- addEventListener 第三個參數 ( true / false )
```javascript
function addEvnet(classNmae) {
document.querySelector(className)
.addEvnetListener('click', function() {
console.log(className, '冒泡')
}, false)
document.querySelector(className)
.addEventListener('click', function() {
console.log(className, '捕獲')
}, true)
}
```
- 延伸 : [DOM 的事件傳遞機制:捕獲與冒泡](https://blog.techbridge.cc/2017/07/15/javascript-event-propagation/)
- 別向上級回報 : stopPropagation
- 在**捕獲**接段停止傳送 :
```javascript
window.addEventListener('click', function(e) {
e.stopPropagation()
}, true)
// 放在所有的 function 前面就可以停止傳送所有事件
```
- 停止**冒泡** :
```javascript
document.querySelector('.btn')
.addEventListener('click', function(e) {
// e.stopPropagation()
e.stopImmediatePropagation()
// 立即停止其他 eventListener,下面就不會觸發
console.log('.btn click1')
})
document.querySelector('.btn')
.addEventListener('click', function(e) {
e.stopPropagation()
// 停止冒泡,還是會觸發 console.log
console.log('.btn click1')
})
```
- 新手 100% 會搞錯得問題
- 常犯的錯誤 :
```javascript
const elements = document.querySelectorAll('.btn')
for (var i = 0; i < elements.length; i++) {
element[i].addEventListener('click', function() {
alert(i + 1)
})
}
// 不管點哪個按鈕都是 alert 最後一個數字
```
- 可以在 button 加入 `data-value='Num'` 屬性 :
```javascript
const elements = document.querySelectorAll('.btn')
for (var i = 0; i < elements.length; i++) {
element[i].addEventListener('click', function() {
alert(e.target.getAttribute('data-value'))
})
}
// e.target.getAttribute 取得 attr
```
- 事件代理 : Event Delegation
- 動態新增按鈕 :
```javascript
let num = 3
// body 上面原本 btn 的數量
document.querySelector('.add-btn').addEventListener('click', function() {
const btn = document.createElement('button')
btn.setAttribute('data-btn', num)
btn.classList.add('btn')
btn.innerText = num
num++
document.querySelector('.outer').appendChild(btn)
})
document.querySelector('.outer').addEventListener('click', function(e) {
// 如果有 btn 屬性才觸發點擊事件
if (e.target.classList.contains('btn')) {
alert(e.target.getAttribute('data-value'))
}
})
```
- 綜合範例 : 簡易密碼產生器
```javascript
document.querySelector('.btn-generate').addEventListener('click', function() {
const elements = querySelectorAll('input[type=checkbox]')
for (let i = 0; i < elements.length; i++) {
if (elements.checked) {
availableChar += elements[i].getAttrbute('data-char')
}
}
let result = ''
for (let i = 0; i < 10; i++) {
const number = Math.floor(Math.random() * availableChar.length)
result += availableChar[number]
}
document.querySelector('.result').innerText = reslut
})
```
- 綜合範例 : 通訊錄管理
```javascript
document.querySelector('.add-btn').addEventListener('click', function() {
const div = document.createElement('div')
div.classList.add('row')
div.innerHTML = `
姓名 : <input name='name' />
電話 : <input name='number />
<button class='delete'>刪除<button />'`
document.querySelector('.contacts').appendChild(div)
})
document.querySelector('.contacts').addEventListener('click', function(e) {
if (e.target.classList.contains('delete')) {
// 移除 .contacts 下面的 .row
document.querySelector('.contacts').removeChild(
e.target.closest('.row')
)
}
})
```
#### 如何在瀏覽器上儲存資料
- Cookie ( 古老 )
- 小型文字檔會自動帶到 sever
- Chrome 開發者工具 > application > Storage > Cookie
- Local Storage ( 推薦 )
- 可以被看作為改進的 Cookie,提供更大的儲存容量
- Chrome 開發者工具 > application > Storage > Local Storage
```javascript
const oldValue = window.localStorage.getItem('text')
document.querySelector('.text').value = oldValue
document.querySelector('button').addEventListener('click', function() {
const value = document.querySelector('.text').value
windwo.localStorage.setItem('text', value)
})
```
- Sessoin Storage ( 一閃即逝,新開 tabe 就不見啦 )
- 基本上跟 Local Storage 一樣,只差存儲的時間
```javascript
const oldValue = window.seccionStorage.getItem('text')
document.querySelector('.text').value = oldValue
document.querySelector('button').addEventListener('click', function() {
const value = document.querySelector('.text').value
windwo.seccionStorage.setItem('text', value)
})
```
#### 網頁與伺服器溝通
- node.js 與 瀏覽器 發 request 的差異是甚麼
- 在瀏覽器上會被限制
- 第一種傳送方式 : form
- 我要帶上某個資訊到頁面
- 第二種傳送方式 : ajax
- AJAX ( Asynchronnous JavaScript And XML )
```javascript
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')
}
// 最後一個 true/false 為是否非同步
request.open('GET', 'https://reqres.in/api/users', true)
request.send()
```
- XMLHttpRequest
```javascript
const request = new XMLHttpRequest()
// 使用 addEventListener 監聽 load 事件
request.addEventListener('load', function() {
if (request.status >= 200 && request.status < 400) {
console.log(request.responseText)
} else {
console.log('err', request.status)
}
})
request.onerror = function() {
console.log('error')
}
// 最後一個 true/false 為是否非同步
request.open('GET', 'https://reqres.in/api/users', true)
request.send()
```
- Same Origin Policy ( 同源政策 ) & CORS ( Cross-Origin Resource Sharing 跨來源資源共用 )
- 相同 domain 就是同源,但 http & https 不同源
- 如果 header 沒有加 `access-control-allow-origin: *` 就絕對無法 CORS 存取
- 瀏覽器的限制
- 同源政策等,都是瀏覽器幫你加的,node.js 則沒有此限制
- 第三種傳送方式 : JSONP ( JSON with Padding )
- `<img src="url" />`, `<script src="url"></script>`不受同源政策影響,因為比較沒有安全上的疑慮
- 所以使用 `<script src="url"></script>` 來拿資料,用 function 的方式傳送資料,不常用
- 單向傳送資料
- 例如 : 如何知道有人打開你的信
```javascript
// 放一張很小或是透明的圖片,如果有人打開就會發 request 到 img 裡的 src
<img width="1" height="1" src="https://example.com/user_open/123"
```
- 綜合範例 : 抓取資料並顯示
```javascript
<style>
.profile {
border: 1px solid #333;
margin-top: 20px;
display: inline-flex;
align-items: center;
}
.profile__name {
margin: 0px 10px;
}
</style>
<body>
<div class='app'>
<div class='profile'>
<div class='profile__name'> George Bluth</div>
<img class='profile__img' src="" />
</div>
</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 < user.length; i++) {
const div = document.createElement('div')
div.classList.add('profile')
div.innetHTML = `
<div class='profile__name'> ${user[i].first_name} ${user[i].last_name}</div>
<img class='profile__img' src="${user[i].avatar}" />`
container.appendChild(div)
}
} else {
console.log('err', request.status)
}
})
request.onerror = function() {
console.log('error')
}
request.open('GET', 'https://reqres.in/api/users', true)
request.send()
</script>
```
- Client Side Rendering
- 使用 JavaScript 動態 render 出來的,檢查原始碼會看不到東西
#### Ref.
- [DOM 的事件傳遞機制:捕獲與冒泡](https://blog.techbridge.cc/2017/07/15/javascript-event-propagation/)
- [網頁儲存 (wiki)](https://zh.wikipedia.org/zh-tw/%E7%BD%91%E9%A1%B5%E5%AD%98%E5%82%A8)
- [Window.localStorage (MDN)](https://developer.mozilla.org/zh-TW/docs/Web/API/Window/localStorage)
- [輕鬆理解 Ajax 與跨來源請求](https://blog.techbridge.cc/2017/05/20/api-ajax-cors-and-jsonp/)
- [同源政策 (Same-origin policy) (MDN)](https://developer.mozilla.org/zh-TW/docs/Web/Security/Same-origin_policy)
- [跨來源資源共用(CORS) (MDN)](https://developer.mozilla.org/zh-TW/docs/Web/HTTP/CORS)
---
#### 心得 2021 May. 25
這門課我覺得老師的架構整理得很好 :
1. 5 種選元素的方法、修改 CSS、修改 Class、修改 text/html、新增/刪除元素
2. 各種事件處理、preventDefault、還有捕獲與冒泡
看到中場總結的時候,老師說其實**會這些就可以做很多事了,只差一點想像力**,聽到這覺得 : 哇,感覺我跟網路世界又更進一步了
如果你好奇到底可以做什麼或是想練習,推薦資源 : [JavaScript 30](https://javascript30.com/) ( 使用純 JavaScript 30 打造 30 件東西 ),學到這應該有能力聽得懂了 :))
3. 資料交換的部分,form、ajax、jsonp 也是三種都有講到
特別是 jsonp ,我是第一次聽到,覺得能想出來的人真的是奇葩XDD
最後總結,老師說 JavaScript 只要熟悉下面三個東西: **UI、事件、資料**,就可以做出所有你想的到的東西了,上完覺得 : 哇,我跟網路的世界只剩一步了,就是寫好 code 後按下 run
---
以下新增 :
- 2021/05/31
html 跳脫 function
```javascript
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
```