什麼是 DOM?
DOM 全名為 Document Object Model,中文譯名為文件物件模型,把 HTML 文件內的各個標籤,包括文字、圖片等等都定義成物件,而這些物件最終會形成一個樹狀結構,圖示如下:
許多公司有設計瀏覽器,故需要一個讓各大瀏覽器都能編譯的規範,而 W3S 定義了許多規範,DOM 是其中一個。
事件傳遞機制的順序是什麼;什麼是冒泡,什麼又是捕獲?
- 捕獲階段
CAPTURING_PHASE
:
由 DOM 樹的最外層依序向內,過程中觸發個別元素的捕獲階段事件監聽。 - 目標階段
AT_TARGET
:
到達事件目標,依照註冊順序觸發事件監聽。 - 冒泡階段
BUBBLING_PHASE
:
由事件目標依序向外,過程中觸發個別元素的冒泡階段事件監聽。
什麼是 event delegation,為什麼我們需要它?
利用事件的冒泡機制 (Event Bubbling),把子節點們的事件統一處理,可避免掛載過多的監聽器。
event.preventDefault() 跟 event.stopPropagation() 差在哪裡,可以舉個範例嗎?
- event.preventDefault():阻止網頁預設事件。
- event.stopPropagation():阻止當前事件繼續捕捉及冒泡階段的傳遞,。
範例如下:
preventDefault & stopPropagation
什麼是 callback funciton?
const element = document.querySelector('#block')
element.addEventListener('click', function(e) {
console.log(e.target)
})
e.target 可拿到上點了哪個元素
const element = document.querySelector('#block')
element.addEventListener('click', function(e) {
console.log(e.target)
})
toggle
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>toggle practice</title>
</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', (e) => {
document.querySelector('body').classList.toggle('active')
})
</script>
</body>
</html>
<style>
.active {
background-color: red;
}
</style>
stopPropagation
- stopPropagation
- stopImediatePropagation
delegation 事件代理機制
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>eventMechanism</title>
</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>
// 事件代理機制 delegation
let num = 3
document.querySelector('.add-btn').addEventListener('click', (e) => {
const btn = document.createElement('button')
btn.setAttribute('data-value', num)
btn.classList.add('btn')
btn.innerText = num
num += 1
document.querySelector('.outer').appendChild(btn)
})
document.querySelector('.outer').addEventListener('click', (e) => {
if (e.target.classList.contains('btn')) {
alert(e.target.getAttribute('data-value'))
}
})
</script>
</body>
</html>
簡易密碼產生器
建立 function
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<style>
body {
font-family: '微軟正黑體';
font-size: 40px;
}
.result {
background-color: lightcyan;
}
</style>
<div class="app">
<div>
<label for="english">
<input type="checkbox" id="english" name="english" />英文
</label>
</div>
<div>
<label for="number">
<input type="checkbox" id="number" name="number" />數字
</label>
</div>
<div>
<label for="special"">
<input type="checkbox" id="special" name="special" />特殊符號
</label>
</div>
<button class="btn-generate">產生</button>
<div class="result"></div>
</div>
<script>
function getChar(name, char) {
if (document.querySelector('input[name=' + name + ']').checked) {
return char
}
}
const BTN = document.querySelector('.btn-generate')
BTN.addEventListener('click', () => {
let availableChar = ''
availableChar += getChar('english', 'abcdefghijklmnopqrstuvwxyz')
availableChar += getChar('number', '0123456789')
availableChar += getChar('special', '!@#$%^&*(')
let result = ''
for (let i=0; i<10; i++) {
let num = Math.floor(Math.random() * availableChar.length)
result += availableChar[num]
}
document.querySelector('.result').innerHTML = result
})
</script>
</body>
</html>
將密碼組成字元放在 html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<style>
body {
font-family: '微軟正黑體';
font-size: 40px;
}
.result {
background-color: lightcyan;
}
</style>
<div class="app">
<div>
<label for="english">
<input type="checkbox" id="english" name="english" data-char="abcdefghijklmnopqrstuvwxyz" />英文
</label>
</div>
<div>
<label for="number">
<input type="checkbox" id="number" name="number" data-char="0123456789" />數字
</label>
</div>
<div>
<label for="special"">
<input type="checkbox" id="special" name="special" data-char="!@#$%^&*(" />特殊符號
</label>
</div>
<div>
<label for="chi"">
<input type="checkbox" id="chi" name="chi" data-char="你是八七" />你是八七
</label>
</div>
<button class="btn-generate">產生</button>
<div class="result"></div>
</div>
<script>
const BTN = document.querySelector('.btn-generate')
BTN.addEventListener('click', () => {
let result = ''
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')
}
}
for (let i=0; i<20; i++) {
let num = Math.floor(Math.random() * availableChar.length)
result += availableChar[num]
}
document.querySelector('.result').innerHTML = result
})
</script>
</body>
</html>
簡易動態 表單通訊錄
closest() 找最近的一層
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body {
font-size: 32px;
}
</style>
</head>
<body>
<div class="app">
<div>
<button class="add-btn">新增聯絡人</button>
</div>
<div class="contacts">
<div class="row">
姓名: <input type="text" name="name">
電話:<input type="text" name="phone">
<button class="delete">刪除</button>
</div>
</div>
</div>
<script>
const addBTN = document.querySelector('.add-btn')
addBTN.addEventListener('click', (e) => {
const contactRow = document.createElement('div')
contactRow.classList.add('row')
contactRow.innerHTML = `
姓名: <input type="text" name="name">
電話:<input type="text" name="phone">
<button class="delete">刪除</button>
`
document.querySelector('.contacts').appendChild(contactRow)
})
document.querySelector('.contacts').addEventListener('click', (e) => {
if (e.target.classList.contains('delete')) {
document.querySelector('.contacts').removeChild(e.target.closest('.row'))
}
})
</script>
</body>
</html>
cookie 是什麼?
cookie 就是小型文字檔,會自動帶到 server。
某些網站為了辨別用戶端的身分,存在用戶端的資料。
localStorage
- setItem()
- getItem()
- removeItem()
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<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', () => {
const value = document.querySelector('.text').value
window.localStorage.setItem('text', value)
})
</script>
</body>
</html>
sessionStorage
只要將上面那段程式碼中的 localStorage 改成 sessionStorage 即可。
差別在於 sessionStorage 新開分頁無法儲存資料。
在 node.js 與 瀏覽器上用 JS 的差異?
request 與 response 會被瀏覽器限制,故要了解瀏覽器上的規則。
交換資料的第一種方式:form
把資料從前端傳到後端的第一種方式,缺點是會換頁面。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body {
font-size: 40px;
}
</style>
</head>
<body>
<div class="app">
<form action="https://google.com/" method="GET">
username: <input type="text" name="username" />
<input type="submit">
</form>
</div>
</body>
</html>
交換資料的第二種方式:ajax
透過 JS 交換資料,AJAX (Asynchronous JavaScript and XML)
任何非同步跟伺服器交換資料的 JS 都可統稱為 AJAX。
XML 是早期常用的資料格式,現今用 JSON,ajax 還是叫 ajax,但不是 XML。
const request = new XMLHttpRequest()
request.onload = function () { // 當 request 拿到結果之後觸發 onload 事件
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()
因瀏覽器的限制,我們無法發 request 到 google.com,而另一個網頁 () 可以,意思是用 JS 發一個 request
request.responseText
const request = new XMLHttpRequest()
request.onload = function () { // 當 request 拿到結果之後觸發 onload 事件
if (request.status >= 200 && request.status < 400) {
console.log(request.responseText)
} else {
console.log(request.status, request.responseText)
}
}
request.onerror = function () {
console.log('error')
}
request.open('GET', 'https://reqres.in/api/users/1242435353', true) // 第三個參數決定是否要非同步
request.send()
// 404 "{}"
Same Origin Policy
- 不同網域即不是同源。
- 有無加密 (http, https)、協定不同,埠號不同、主機不同,皆是不同源。
- 除非共用同一個 domain,否則別人的網站就是不同源。
跨來源資源共用 (CORS)
cross-origin resource share
如果想要跨來源存取,在 reponse 裡面要加上 header:access-control-allow-origin,這個裡面的內容決定哪些來源的人可以存取。
這個來源是當我們發 request 的時候,瀏覽器會自動在 Request Headers 加上一個 header,origin:
,server 端根據 header 的內容 (來源),決定是否給予權限。
- access-control-allow-origin: *
所有人皆可存取。
瀏覽器的限制
瀏覽器的許多限制幾乎是「安全性考量」。
若利用 node.js 來發 request,因沒有透過瀏覽器,故也沒有限制,可拿到資料。
交換資料的第二種方式:JSONP
JSON with padding,回傳的資料是 JS object,在 server 端做填充,在 client 端可利用 funciton 拿到資料。
利用 script 標籤不受同源政策規範的特性,在 src 裡寫 js
<script>
function setData(users) {
console.log(users)
}
</script>
<script src="https://test.com/user.js"></script>
// 可動態產生 script
const element = document.createElement('script')
element.src = 'https://test.com/user.js?id=1'
// user.js
setData(
{
id: 1,
name: 'hello'
}
)
- 以下標籤不受同源政策影響:
- img
- 可引入其他 JS
- AJAX 一定受同源政策管理
單向傳送資料
應用 email 開信的追蹤、廣告的追蹤。
在 email 上放一張透明的圖片,當使用 email,圖片顯示出來後發一個 request 到該網址,server 端知道有人打開該網址,也就是知道有人打開這封信。
<img width="1" heihgt="1" src="https://example.com/users_open/123456"></img>
實作:抓取資料並顯示
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body {
font-size: 38px;
}
.profile {
border: 1px solid #000;
display: inline-flex;
margin: 20px;
align-items: center;
}
.profile__name {
margin: 0 20px;
}
</style>
</head>
<body>
<div class="app"></div>
<script>
const request = new XMLHttpRequest()
const container = document.querySelector('.app')
request.onload = function () { // 當 request 拿到結果之後觸發 onload 事件
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}" alt="">
`
container.appendChild(div)
}
} else {
console.log(request.status, request.responseText)
}
}
request.onerror = function () {
console.log('error')
}
request.open('GET', 'https://reqres.in/api/users', true) // 第三個參數決定是否要非同步
request.send()
</script>
</body>
</html>
render 的結果:
內容產生的地方在 client 還是 server?
clinet side rendering
用 JS render 頁面,JS 動態產生頁面。缺點是:通常搜尋引擎不會幫忙執行 JS,故搜尋引擎看不到東西
總結
用 JS 改變介面、利用 JS 事件監聽、跟 server 交換資料。