瀏覽器的 JavaScript

簡介

名詞

  • Client-side Render(CSR) : API get data 再由 JavaScript 渲染
  • Server-side Render(SSR) : 在 Server-side 就先渲染成 HTML
  • Search Engine Optimization(SEO) : 搜尋引擎最佳化,透過了解搜尋引擎的運作規則來調整網站,以及提高目的網站在有關搜尋引擎內排名的方式

JavaScript 的功能

  • 介面 : 改變介面
  • 事件 : 監聽事件並做出反應
  • 資料 : 與伺服器交換資料

執行

放於 body 最後
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.box {
background: orange;
height: 100px;
width: 100px;
}
</style>
</head>
<body>
<div class="box"></div>

<script>
document.querySelector('.box')
.addEventListener('click', function(){
document.querySelector('.box').style.background = 'red'
})
</script>
</body>
</html>
放於 head 最後, 但要加入 DOM loaded(因執行時 DOM 尚未 load 完成)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html lang="en">
<head>
......
<script>
// DOM loaded
document.addEventListener('DOMContentLoaded', function(){
document.querySelector('.box')
.addEventListener('click', function(){
document.querySelector('.box').style.background = 'red'
})
})
</script>
</head>
<body>
<div class="box"></div>
</body>
</html>
head 加入js 檔
1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en">
<head>
......
<!-- add js file -->
<script src="main.js"></script>
<title>Document</title>
</head>
<body>
<div class="box"></div>
</body>
</html>
1
2
3
4
5
6
7
8
// main.js
// DOM loaded
document.addEventListener('DOMContentLoaded', function(){
document.querySelector('.box')
.addEventListener('click', function(){
document.querySelector('.box').style.background = 'red'
})
})

DOM

文件物件模型(Document Object Model, DOM)是 HTML、XML 和 SVG 文件的程式介面。它提供了一個文件(樹)的結構化表示法,並定義讓程式可以存取並改變文件架構、風格和內容的方法。

圖片來自 Wikipedia DOM

DOM API

選元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.box {
background: orange;
height: 100px;
width: 100px;
}
</style>
</head>
<body>
<div id="item1" class="box"></div>
<div class="box box2"></div>
<div class="box"></div>

<script>
// 依類別抓取,抓取多個 - HTMLCollection
const elements = document.getElementsByTagName('div')
console.log(elements)
console.log(document.getElementsByClassName('box'))
console.log(document.getElementById('item1'))
// 依 selector 抓取,緊抓取第一個
console.log(document.querySelector('div'))
console.log(document.querySelector('.box2'))
console.log(document.querySelector('#item1'))
// 依 selector 抓取,緊抓多個 - NodeList
const elementsNode = document.querySelectorAll('div')
console.log(elementsNode)
</script>
</body>
</html>
.parentElement

parentElement is the parent element of the current node.
若無父元素或非element則傳回 null

.parentNode

parentNode is the parent of the current node. The parent of an element is an Element node, a Document node, or a DocumentFragment node.
若無父元素或非element則傳回 null

改變 css
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.box {
background: orange;
width: 100px;
}
</style>
</head>
<body>
<div id="item1" class="box">111</div>
<div class="box box2">222</div>
<div class="box">333</div>

<script>
const element = document.querySelector('.box2')
element.style.background = "red"
element.style.margin = "10px"
// 元件使用類陣列是輸入
element.style['padding-top'] = '10px'
// 命名改為駝峰式
element.style.paddingBottom = '20px'
</script>
</body>
</html>
改變 class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.box {
background: orange;
width: 100px;
height: 100px;
}
.active {
background: red;
}
</style>
</head>
<body>
<div id="item1" class="box">111</div>
<div class="box box2 active">222</div>
<div class="box box3">333</div>

<script>
document.querySelector('#item1')
.addEventListener('click', function(){
// toggle class
document.querySelector('#item1').classList.toggle('active')
})
document.querySelector('.box2')
.addEventListener('click', function(){
// remove class
document.querySelector('.box2').classList.remove('active')
})
document.querySelector('.box3')
.addEventListener('click', function(){
// add class
document.querySelector('.box3').classList.add('active')
})
</script>
</body>
</html>
check 是否包含某class
1
alert(div.classList.contains("foo"));
改變內容 innerText, innerHTML, outerHTML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.box {
background: orange;
width: 200px;
font-size: 36px;
/* height: 100px; */
}
.active {
background: red;
}
</style>
</head>
<body>
<div id="item1" class="box">
111
<a href="#">hello</a>
</div>
<div class="box box2 active">
222
<a href="#">hello</a>
</div>
<div class="box box3">
333
<a href="#">hello</a>
</div>

<script>
document.querySelector('#item1')
.addEventListener('click', function(){
element = document.querySelector('#item1')
// 內部之文字
console.log(element.innerText)
element.innerText = 'innerText'
})
document.querySelector('.box2')
.addEventListener('click', function(){
// 內部之所有內容, 含 Tag
element = document.querySelector('.box2')
console.log(element.innerHTML)
element.innerText = 'innerHTML'
})
document.querySelector('.box3')
.addEventListener('click', function(){
element = document.querySelector('.box3')
// 外部之父元素
console.log(element.outerHTML)
element.outerHTML = 'outerHTML'
})
</script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// use innerHTML
sendAjaxhApi('http://localhost/robert/comment/api_get_comment.php', function(response){
const comments = JSON.parse(response)
console.log(comments.comment[0])
let section = document.querySelector('section')
for(let i=0 ; i < comments.comment.length ; i++ ) {
let card = document.createElement('div')
card.classList.add('card')
card.innerHTML = `
<div class="card-avatar"></div>
<div class="card-info">
<div class="card-title">
<span class="card-user">${comments.comment[i].nickname}</span>
<span class="card-time">${comments.comment[i].create_at}</span>
<div class="card-content">
${comments.comment[i].content}
</div>
</div>
`
section.appendChild(card)
}
})
加入刪除 元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.box {
background: orange;
width: 200px;
font-size: 36px;
}
</style>
</head>
<body>
<div class="box">
111
<a href="#">hello</a>
</div>

<script>
element = document.querySelector('.box')
// remove tag a
element.removeChild(document.querySelector('a'))

// create element and content
const item = document.createElement('div')
item.innerText = '123'
// add tag div
element.appendChild(item)

// create text
const itemText = document.createTextNode("999")
// add test
element.appendChild(itemText)
</script>
</body>
</html>
.append()

加入Node物件或DOMString(文字)

1
2
3
4
5
6
7
/* 加入Node物件 */
const parent = document.createElement('div')
const child = document.createElement('p')
parent.append(child) // 然後div看起來像這樣<div> <p> </ p> </ div>
/* 加入DOMString(文字) */
const parent = document.createElement('div')
parent.append('附加文字') // 然後div看起來像這樣 <div>附加文字</ div>
.appendChild()

僅能加入Node物件, 不能加入DOMString(文字)
若加入的物件已存在,則會移動到此

1
2
3
4
5
6
7
/* 加入Node物件 */
const parent = document.createElement('div')
const child = document.createElement('p')
parent.appendChild(child) // 然後div看起來像這樣 <div> <p> </ p> </ div>
/* 加入DOMString(文字) */
const parent = document.createElement('div')
parent.appendChild('Appending Text') // Uncaught TypeError: Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'
.remove() - 直接移除元件
1
2
var el = document.getElementById('div-02');
el.remove(); // Removes the div with the 'div-02' id

Event

addEventListener
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.box1 {
background: orange;
width: 200px;
font-size: 36px;
margin: 5px 0;
}
.box2 {
background: orange;
width: 200px;
font-size: 36px;
}
</style>
</head>
<body>
<div class="box1">
111
<a href="#">hello</a>
</div>
<div class="box2">
222
</div>
<input class="input-text" type="text">

<script>
const element = document.querySelector('.box1')
// callback function
element.addEventListener('click', handleBox1);
function handleBox1(e) {
alert('Box1')
// e.target 表 trigger event 的 element
console.log(e.target)
}

document.querySelector('.box2')
// 匿名函數(Anonymous function)
.addEventListener('click', function(){
alert('Box2')
})

document.querySelector('.input-text')
.addEventListener('keypress', function(e){
console.log(e)
// e.key:字元, e.keyCode:編碼, e.code:字串表示
console.log(e.key, e.keyCode, e.code)
})
</script>
</body>
</html>
click
mousedown
mouseup
dbclick - 短時間內雙擊左鍵觸發
contextmenu - 滑鼠右鍵點擊觸發
mousemove - 滑鼠移動時觸發(要用到時才綁定,避免不斷觸發)
mouseenter - 滑鼠進入元素邊界時觸發(不會 bubble)
mouseleave - 滑鼠完全離開元素時觸發(不會 bubble)
mouseover - 滑鼠經過不同元素時觸發
mouseout - 滑鼠離開元素時觸發
keypress
1
2
3
4
/* e.key : 字元
e.keyCode : 編碼 ascii code
e.code : 字串表示 "KeyQ" for the Q
*/
keydown
keyup
submit - form commit
DOMContentLoaded - DOM load complete
1
2
3
4
// DOM loaded
document.addEventListener('DOMContentLoaded', function(){
......
})
表單處理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
body {
font-size: 24px;
}
</style>
</head>
<body>
<form class="login-form" action="">
<div>
name : <input type="text" name="username">
</div>
<div>
password : <input type="password" name="password">
</div>
<div>
password again : <input type="password" name="password2">
</div>
<input type="submit" value="送出">
</form>

<script>
document.querySelector('.login-form')
.addEventListener('submit', function (e) {
const input1 = document.querySelector('input[name=password]')
const input2 = document.querySelector('input[name=password2]')
// .value 取出值
console.log(input1.value, input2.value)
// 比較密碼
if (input1.value != input2.value) {
// 阻止預設功能(不送出表單)
e.preventDefault()
alert('密碼不同')
}
})
</script>
</body>
</html>
阻止預設行為
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
body {
font-size: 24px;
}
</style>
</head>
<body>
<div>
111
<a href="#">hello</a>
</div>

<script>
document.querySelector('a').addEventListener('click', function(e){
// 阻止預設行為
e.preventDefault();
})
</script>
</body>
</html>
事件傳遞機制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.outer {
width: 200px;
height: 200px;
background: yellow;
}
.inner {
width: 100px;
height: 100px;
background: orange;
}

</style>
</head>
<body>
<div class="outer">
<div class="inner">
<button class="button">Click</button>
</div>
</div>

<script>
addEvent('.outer')
addEvent('.inner')
addEvent('.button')

function addEvent(className) {
document.querySelector(className)
.addEventListener('click', function(){
console.log(className)
})

}
</script>
</body>
</html>
詳細事件傳遞機制:捕獲與冒泡

圖片來自 W3C

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.outer {
width: 200px;
height: 200px;
background: yellow;
}
.inner {
width: 100px;
height: 100px;
background: orange;
}

</style>
</head>
<body>
<div class="outer">
<div class="inner">
<button class="button">Click</button>
</div>
</div>

<script>
addEvent('.button')
addEvent('.inner')
addEvent('.outer')

function addEvent(className) {
// 第三個參數設為 true, 表示捕獲
document.querySelector(className)
.addEventListener('click', function(){
console.log(className, '捕獲')
}, true)

// 第三個參數設為 false, 表示冒泡
document.querySelector(className)
.addEventListener('click', function(){
console.log(className, '冒泡')
}, false)
}
</script>
</body>
</html>
別向上級回報:stopPropagation()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.outer {
width: 200px;
height: 200px;
background: yellow;
}
.inner {
width: 100px;
height: 100px;
background: orange;
}

</style>
</head>
<body>
<div class="outer">
<div class="inner">
<button class="button">Click</button>
</div>
</div>

<script>
addEvent('.button')
addEvent('.inner')
addEvent('.outer')

function addEvent(className) {
// 第三個參數設為 true, 表示捕獲
document.querySelector(className)
.addEventListener('click', function(e){
console.log(className, '捕獲')
}, true)

// 第三個參數設為 false, 表示冒泡
document.querySelector(className)
.addEventListener('click', function(e){
// .button stopPropagation
if (className === '.button') {
e.stopPropagation()
}
console.log(className, '冒泡')
}, false)
}
</script>
</body>
</html>
多重觸發與抑制(使用 stopImmediatePropagation())

多重觸發

1
2
3
4
5
6
7
8
9
10
11
<script>
document.querySelector('.button')
.addEventListener('click', function(e){
console.log("button click 1")
})

document.querySelector('.button')
.addEventListener('click', function(e){
console.log("button click 2")
})
</script>

抑制後觸發

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script>
document.querySelector('.button')
.addEventListener('click', function(e){
// 要抑制另一個 stopPropagation() 無效, 要用 stopImmediatePropagation()
// e.stopPropagation()
e.stopImmediatePropagation()
console.log("button click 1")
})

document.querySelector('.button')
.addEventListener('click', function(e){
console.log("button click 2")
})
</script>
錯誤的 event

for 使用 var 為錯誤, let 即正確

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.btn {
font-size: 24px;
}

</style>
</head>
<body>
<div class="outer">
<button class="btn">1</button>
<button class="btn">2</button>
<button class="btn">3</button>
<button class="btn">4</button>
<button class="btn">5</button>
</div>

<script>
const elements = document.querySelectorAll('.btn')
// 若使用 var 就不正常 --> alart always 6
// let 就正常 --> alart 1~5
// for (var i=0 ; i< elements.length ; i++) {
for (let i=0 ; i< elements.length ; i++) {
elements[i].addEventListener('click', function(){
alert(i+1)
})
}
</script>
</body>
</html>

使用 html data-xx attribute

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.btn {
font-size: 24px;
}

</style>
</head>
<body>
<div class="outer">
<button class="btn" data-value="a">1</button>
<button class="btn" data-value="2">2</button>
<button class="btn" data-value="3">3</button>
<button class="btn" data-value="4">4</button>
<button class="btn" data-value="5">5</button>
</div>

<script>
const elements = document.querySelectorAll('.btn')
for (let i=0 ; i< elements.length ; i++) {
elements[i].addEventListener('click', function(e){
alert(e.target.getAttribute('data-value'))
})
}
</script>
</body>
</html>

add new button

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.add-btn, .btn {
font-size: 24px;
}

</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 (let 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.classList.add('btn')
btn.setAttribute('data-value', num)
btn.innerText = num++
document.querySelector('.outer').appendChild(btn)
})
</script>
</body>
</html>
event delegation(代理)

事件代理(Event Delegation),又稱之為事件委託

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.add-btn, .btn {
font-size: 24px;
}

</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
document.querySelector('.add-btn')
.addEventListener('click', function(){
const btn = document.createElement('button')
btn.classList.add('btn')
btn.setAttribute('data-value', num)
btn.innerText = 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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.item {
border: 1px solid #000;
}
</style>
</head>
<body>
<ul class="list">
<li class="item" id="id1">item1</li>
<li class="item">item2</li>
<li class="item">item3</li>
</ul>
<script>
document.addEventListener('click', function(e){
let element = e.target
if (element.classList.contains('item')) {
console.log(element.innerText)
}
// 判斷 tag
// if(element.nodeName.toLowerCase === "li")
// 判斷 id
if(element.id === "id1") {
console.log(element.id)
}

})
</script>
</body>
</html>
DOM loaded
1
2
3
4
// DOM loaded
document.addEventListener('DOMContentLoaded', function(){
......
})

Web API

Element

Attribute
1
2
3
4
5
6
7
8
9
// get attribute 
// var attribute = element.getAttribute(attributeName);
var div1 = document.getElementById("div1");
var align = div1.getAttribute("align");
// set attibute
// Element.setAttribute(name, value)
var b = document.querySelector("button");
b.setAttribute("name", "helloButton");
b.setAttribute("disabled", "");
Input checkbox 屬性
1
2
3
4
5
// 被選到
document.getElementById("check1").checked=true
if (document.getElementById("check1").checked){
consloe.log('It's selected.)
}
parentElement 父元素
1
var x = document.getElementById("myLI").parentElement
.click() 模擬滑鼠點擊一個元素
1
2
// click 1st nav link
document.querySelector('.navbar a').click()

Event

.target - 初觸發事件的物件
tag name
1
2
3
4
5
document.querySelector('.box')
.addEventListener('click', function(e){
// dump tag name
console.log(e.target.tagName.toLowerCase())
}) // div , ul or li
.currentTarget - 處理事件之監聽器所屬的物件

XMLHttpRequest

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const request = new XMLHttpRequest()
// loaded function
request.onload = function() {
if (request.status >= 200 && request.status < 400) {
console.log(request.responseText)
}
else {
console.log('response error :', request.status)
}
}
// error
request.onerror = function() {
console.log('error')
}
// true 表非同步
request.open('GET', 'https://google.com', true)
request.send()

URLSearchParams 解析網址的參數

1
2
3
// http://localhost/robert/todo_list_bs4/?id=22
let searchParams = new URLSearchParams(window.location.search);
let id = searchParams.get('id');

History API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 歷史紀錄中往上一步移動
window.history.back();
// 往下一步移動
window.history.forward();
// 移動到特定的歷史紀錄-往前一頁
window.history.go(-1);
// 往後一頁
window.history.go(1);
// 更改網址 - 不更新內容
history.pushState(null, null, '/hello')

pushState() // 更改網址,但不會讓瀏覽器去載入
replaceState() // 修改目前的歷史紀錄而不是創造一個新的
popstate 事件 // popstate 事件的 state 屬性會含有一個歷史紀錄的 state object 的副本。

fetch and promise

fetch API

Fetch API 提供了一個能獲取包含跨網路資源在的資源介面,它有點像我們所熟悉的 XMLHttpRequest,回傳為 Promise 的物件

fetch example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
// simple run fetch
const result = fetch('https://run.mocky.io/v3/d2268e25-bfa4-4e70-a505-b5faf61f7047')
console.log(result)
// response Promise
// --> Promise {<pending>}

// fetch().then() 將 promise 內容傳至function
function printResult(resp) {
console.log(resp)
}
// ------------
const result = fetch('https://run.mocky.io/v3/d2268e25-bfa4-4e70-a505-b5faf61f7047')
result.then(printResult)
// --> Response {type: "cors", url: "https://run.mocky.io/v3/d2268e25-bfa4-4e70-a505-b5faf61f7047",

// response status
api500 = "https://run.mocky.io/v3/db4c7417-2cf8-473e-9f05-f16ec2cfe725"
api200 = 'https://run.mocky.io/v3/d2268e25-bfa4-4e70-a505-b5faf61f7047'
fetch(api500).then( response => {
console.log(response.status)
})
fetch(api200).then( response => {
console.log(response.status)
})
// 500
// 300

// text() show response
// fetch() => Promise
// response.text() => Promise
api200 = 'https://run.mocky.io/v3/d2268e25-bfa4-4e70-a505-b5faf61f7047'
fetch(api200).then( response => {
response.text().then( text => {
console.log(text)
})
// console.log(response.text())
})
// {"message": "Hello!"}

// json() show response
// return send to .then
// fetch() => Promise
// response.text() => Promise, text = json
// response.json() => Promise, text = object
api200 = 'https://run.mocky.io/v3/d2268e25-bfa4-4e70-a505-b5faf61f7047'
fetch(api200).then( response => {
response.json().then(text =>{
console.log(text)
console.log(text.message)
return 123
}).then( abc =>{
console.log('abc=', abc)
})
})
// {message: "Hello!"}
// Hello!
// abc=123

// json() show response (another style)
// return send to .then
// fetch() => Promise
// response.text() => Promise, text = json
// response.json() => Promise, text = object
api200 = 'https://run.mocky.io/v3/d2268e25-bfa4-4e70-a505-b5faf61f7047'
fetch(api200).then( response => {
return response.json()
}).then(json =>{
console.log(json)
})
// {message: "Hello!"}

// uri error trigger catch
// return send to .then
// fetch() => Promise
// response.text() => Promise, text = json
// response.json() => Promise, text = object
api200 = 'https://run.mocky.io/v3/d2268e25-bfa4-4e70-a505-b5faf61f7047-err'
fetch(api200).then( response => {
return response.json()
}).then(json =>{
console.log(json)
}).catch(e =>{
console.log("error=", e)
})
// error= TypeError: Failed to fetch

// POST
data = {
age: 30
}
pi200 = 'https://run.mocky.io/v3/d2268e25-bfa4-4e70-a505-b5faf61f7047'
fetch(pi200, {
body: JSON.stringify(data), // object to json
headers: {
'content-type': 'application/json' // json format
},
method: 'POST', // *GET, POST, PUT, DELETE, etc.
}).then(response => {
return response.json()
}).then( json =>{
console.log(json)
})
// {message: "Hello!"}
fetch 注意事項
  • content-type 要填正確,如以下為 json
1
2
3
4
5
6
7
8
9
10
11
12
pi200 = 'https://run.mocky.io/v3/d2268e25-bfa4-4e70-a505-b5faf61f7047'
fetch(pi200, {
body: JSON.stringify(data), // object to json
headers: {
'content-type': 'application/json' // json format
},
method: 'POST', // *GET, POST, PUT, DELETE, etc.
}).then(response => {
return response.json()
}).then( json =>{
console.log(json)
}
  • fetch 不會傳送 cookies,除非你有設定 credential
    • 要讓瀏覽器將 credentials 跟著 request 一起送出, 方式就是在 init object 加上 credentials: ‘include’
    • 想要把 credentials 發送給同源的 URL ,加上credentials: ‘same-origin’。
    • 要確保瀏覽器不會帶著 credentials 請求,可以用 credentials: ‘omit’ 。
1
2
3
4
5
6
7
8
9
10
11
fetch('https://example.com', {
credentials: 'include'
})

fetch('https://example.com', {
credentials: 'same-origin'
})

fetch('https://example.com', {
credentials: 'omit'
})
  • mode: ‘no-cors’僅告訴 server, don’t card CPRS don’n need response error,並不表示server 不support時,能收到資料
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// mode: 'no-cors'
data = {
age: 30
}
pi200 = 'https://tw.yahoo.com'
fetch(pi200, {
body: JSON.stringify(data), // object to json
headers: {
'content-type': 'application/json' // json format
},
method: 'POST', // *GET, POST, PUT, DELETE, etc.
mode: 'no-cors', // no-cors, cors, *same-origin
}).then(response => {
return response.text()
.then( text =>{
console.log(text)
})
})
Promise
Promise應用 clipboard
1
2
3
4
5
// 只能在 active tab 發生作用 (aka 開發者無法在 colsole 做測試,會得到DOMException: Document is not focused.)
var promise = navigator.clipboard.readText()
promise.then(data =>{
console.log()
})
Promise example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// 1st Promise example
// function init(resolve, reject) {
// // resolve(5) // data= 5, return for then
// reject(3) // error= 3, return for catch
// }
// const myPromise = new Promise(init)
// sampe function as up example
const myPromise = new Promise( (resolve, reject) => {
resolve(4)
})
myPromise.then( data => {
console.log('data=', data)
}).catch(err =>{
console.log('error=' , err)
})

// set for 3s deleay
const myPromise = new Promise( (resolve, reject) => {
setTimeout(() => {
resolve(6)
}, 3000)
})
myPromise.then( data => {
console.log('data=', data)
}).catch(err =>{
console.log('error=' , err)
})

// example for XMLHttpRequest()
api200 = 'https://run.mocky.io/v3/d2268e25-bfa4-4e70-a505-b5faf61f7047'
const myPromise = new Promise((resolve, reject) => {
var request = new XMLHttpRequest()
request.open('GET', api200, true)
// --------------
request.onload = function() {
if (this.status >= 200 && this.status < 400) {
var data = JSON.parse(this.response)
resolve(data)
}
}
request.onerror = function(err) {
reject(err)
}
request.send()
})
// ----------------
myPromise.then( data => {
console.log('myPromise data =', data)
}).catch(err => {
console.log('error =', err)
})
// myPromise data = {message: "Hello!"}

// sleep()
// function sleep(ms) {
// const myPromise = new Promise(resolve => {
// setTimeout(resolve, ms)
// })
// return myPromise
// }
// ---------------
// function sleep(ms) {
// return new Promise(resolve => {
// setTimeout(resolve, ms)
// })
// }
// ---------------
// const sleep = ms => new Promise(resolve => {
// setTimeout(resolve, ms)
// })
//---------------
const sleep = ms =>
new Promise(resolve => setTimeout(resolve, ms))
sleep(1500).then( data => {
console.log('myPromise data', data)
})
.catch (err => {
console.log('err =', err)
})
// myPromise data undefined

async and await

  • await 特性下,會等 promise 任務完成後才會讓程式繼續往下執行
  • async,他能夠將 await 包在裡面,被包在裡面的 await 會依序地執行
example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// for fetch
const response = await fetch(...)
console.log(response)

// for sleep
const sleep = ms =>
new Promise(resolve => setTimeout(resolve, ms))
async function main() {
console.log('enter main')
await sleep(1000)
console.log('exit main')
}
main()

// wait then get data
const sleep = ms =>
new Promise(resolve => setTimeout(resolve, ms))
function getData(){
const api200 = 'https://run.mocky.io/v3/d2268e25-bfa4-4e70-a505-b5faf61f7047'
return fetch(api200)
.then( response => {
return response.json()
}).then(json =>{
console.log(json)
})
}
async function main() {
console.log('enter main')
await sleep(1000)
try {
const result = await getData()
} catch(err) {
console.log(err)
}
console.log('exit main')
}
main()

瀏覽器上儲存資料

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
</style>
</head>
<body>
<div>
<div>
id : <input class="id" type="text">
</div>
<div>
phone : <input class="phone" type="text">
</div>
<button class="btn-save">儲存</button>
</div>

<script>
function setCookie(cname, cvalue, exdays) {
let d = new Date()
// ms
d.setTime(d.getTime() + exdays*24*60*60*1000)
let expires = 'expires=' + d.toGMTString()
document.cookie = cname + '=' + cvalue + ';' + expires + ';path=/' ;
}

function getCookie(cname) {
let name = cname + '='
let decodeCookie = decodeURIComponent(document.cookie)
let ca = decodeCookie.split(';')
for (let i=0 ; i < ca.length ; i++) {
let c = ca[i]
c = c.trimStart(c)
if (c.indexOf(name) === 0) {
return c.substring(name.length, c.length)
}
}
return ''
}

// get cookie
const oldId = getCookie('user-id')
document.querySelector('.id').value = oldId
document.querySelector('.phone').value = getCookie('phone')
// save cookie trigger
document.querySelector('.btn-save')
.addEventListener('click', function(){
const id = document.querySelector('.id').value
// set cookie
setCookie('user-id', id, 7)
setCookie('phone', document.querySelector('.phone').value, 7)
})
</script>
</body>
</html>

local storage

儲存與 server 無關資料

simple example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
</style>
</head>
<body>
<div>
id : <input class="id" type="text">
<button class="btn-save">儲存</button>
</div>

<script>
// get local storage
const oldId = window.localStorage.getItem('id')
document.querySelector('.id').value = oldId

document.querySelector('.btn-save')
.addEventListener('click', function(){
const id = document.querySelector('.id').value
// set local storage
window.localStorage.setItem('id', id)
})
</script>
</body>
</html>
save job list
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
</style>
</head>
<body>
<div>
<div>
job : <input class="job" type="text">
<button class="btn-add">新增</button>
</div>
<div class="list">
</div>
</div>

<script>
let jobList = JSON.parse(window.localStorage.getItem('todo-list'))
let idIndex = Number(JSON.parse(window.localStorage.getItem('id-index')))
// init value for 1st time
if (jobList === null) {
jobList = []
}
if (idIndex === 0) {
idIndex++
}
// show list
showList(jobList)
document.querySelector('.btn-add')
.addEventListener('click', function() {
let item = {}
item.job = document.querySelector('.job').value
if (item.job !== '') {
item.id = idIndex++
jobList.push(item)
// convert job array to JSON
let jobListJson = JSON.stringify(jobList)
window.localStorage.setItem('todo-list', jobListJson)
window.localStorage.setItem('id-index', String(idIndex))
// update list
showList(jobList)
}
else {
console.log('Input error')
}
})
// update job list
function showList(job) {
const listDiv = document.querySelector('.list')
listDiv.innerText = ''
for (let i=0 ; i < jobList.length ; i++) {
let item = document.createElement('div')
item.innerText = `${job[i].id} : ${job[i].job}`
listDiv.appendChild(item)
}
}
</script>
</body>
</html>

session storage

網頁關掉即清除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
</style>
</head>
<body>
<div>
id : <input class="id" type="text">
<button class="btn-save">儲存</button>
</div>

<script>
// get session storage
const oldId = window.sessionStorage.getItem('id')
document.querySelector('.id').value = oldId

document.querySelector('.btn-save')
.addEventListener('click', function(){
const id = document.querySelector('.id').value
// set session storage
window,sessionStorage.setItem('id', id)
})
</script>
</body>
</html>

傳送資料

表單 from –> Browser 畫面更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div class="app">
<!-- action: 表單提交到哪裡(也可能是後端程式) -->
<!-- <form method="GET" action="/test"> -->
<form method="POST" action="https://google.com">
username: <input type="text" name="username">
<input type="submit">
</form>
</div>
</body>
</html>

AJAX(Asynchronous JavaScript and XML) –> 資料回傳至 JavaScript

AJAX google - 瀏覽器的限制 同源策略(Same-origin policy)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>

<script>
const request = new XMLHttpRequest()
// loaded function
request.onload = function() {
if (request.status >= 200 && request.status < 400) {
console.log(request.responseText)
}
else {
console.log('response error :', request.status)
}
}

// error
request.onerror = function() {
console.log('error')
}

// true 表非同步
request.open('GET', 'https://google.com', true)
request.send()
</script>
</body>
</html>
AJAX fake data - Access-Control-Allow-Origin : *

CORS(全名為 Cross-Origin Resource Sharing) 跨來源資源共享 : server response head 加 Access-Control-Allow-Origin : *

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>

<script>
const request = new XMLHttpRequest()
// loaded function
// type 1
// request.addEventListener('load', function() {
// if (request.status >= 200 && request.status < 400) {
// console.log(request.responseText)
// }
// else {
// console.log('response error :', request.status)
// }
// })
// type 2
request.onload = function() {
if (request.status >= 200 && request.status < 400) {
console.log(request.responseText)
}
else {
console.log('response error :', request.status)
}
}

// error
request.onerror = function() {
console.log('error')
}

// true 表非同步
request.open('GET', 'https://reqres.in/api/users/2', true)
request.send()
</script>
</body>
</html>
AJAX set header
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function sendTwitchApi(url, respProcess) {
const request = new XMLHttpRequest()
// loaded function
request.onload = function() {
if (request.status >= 200 && request.status < 400) {
// process by callback function
respProcess(request.responseText)
}
else {
console.log('response :', request)
console.log('response error :', request.status)
}
}

// error
request.onerror = function() {
console.log('error')
}

// true 表非同步
request.open('GET', url, true)
// add header
request.setRequestHeader('Accept','application/vnd.twitchtv.v5+json');
request.setRequestHeader('Client-ID','qvtuq71csrlv5ipxo1ljzgbzqn1okh');
request.send()
}

JSONP(JSON with Padding) 第三種方式資料交換方式

  • img, scrip不受同源限制
  • 使用 script 傳回 json data 即可不受 同源策略 限制
  • 若 json 外部可包一個 function 即可方便處理,故要 server 配合
  • 某些 server 可指定 callback function(如 Twitch)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script>
function receiveData (response) {
console.log(response);
}
</script>
<!-- <script src="https://www.game.com/api/user"></script> -->
<script>
receiveData(
{
name: 'Robert',
age: 30
}
)
</script>

單向資料傳送(email 或 廣告 追蹤)

擺一張小而透明的檔案,若網頁被打開,server就知道 email/廣告 被打開

Window

method

alert()

Displays an alert dialog

1
2
3
// window 可省略
window.alert("hello!")
alert("hello2 !")
load 畫面
1
2
3
4
// 刷新當前頁面
location.reload()
// load 指定頁面
window.location = 'index.html?id=' + respId
window load complete
1
2
3
window.onload = function() {
......
}

Snippet for JavaScript

escape HTML
1
2
3
4
5
6
7
function escapeHTML(s) { 
return s.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/'/g, "&#039;");
}