在瀏覽器上執行 JavaScript 的方式
- 在 HTML 檔案裡面,用
<script></script>
的標籤,裡面包 JavaScript 的程式碼,如果<script>
在<body>
的前面,可以用document.addEventListener("DOMContentLoaded", function(){})
先去解析 body 裡面的標籤(DOM) - 或用
<script src= './demo.js'></script>
,並將程式碼也在 demo.js 這個檔案中./
是相對路徑的意思,表示皆為在同個資料夾下的檔案
DOM (Document Object Model)
簡介
- 把 document 轉換成物件,物件會變成樹狀結構,樹狀結構由節點(node)組成,像是文字或標籤。瀏覽器提供 DOM 作為橋樑,讓 JavaScript 改變畫面上的東西。JavaScript 可以透過 DOM 拿到某個物件(元素),並針對該物件作改變。
如何選擇想要的元素
程式介紹
getElementsByTagName
<script>
如果在 hello 的上面,會跑不出來,因為瀏覽器是由上至下跑程式碼,用 dev tool 會顯示空陣列:
- 將
<script>
移到下面後:
<body>
<div>
hello
</div>
<script>
const elements = document.getElementsByTagName('div')
console.log(elements)
</script>
</body>
- 也可以印出特定元素
<body>
<div>
hello
</div>
<div>
哩後
</div>
<script>
const elements = document.getElementsByTagName('div')
console.log(elements[1])
</script>
</body>
getElementsByClassName
- 範例:
<body>
<div class='block'>
hello
</div>
<div>
哩後
</div>
<script>
const elements = document.getElementsByClassName('block')
console.log(elements)
</script>
</body>
getElementById
- 後面接 ID
querySelector
- 後面接 CSS 的 selector
- 只會選到一個元素
<body>
<div class='block'>
hello
</div>
<div>
哩後
</div>
<script>
const elements = document.querySelector('.block')
console.log(elements)
</script>
</body>
querySelectorAll
- 會選到多個符合的元素,用類似陣列的形式表示
改變 CSS
- 後面不能接
-
,所以要改成駝峰式寫法:
<script>
const element = document.querySelector('.block2')
element.style.paddingTop = '30px'
</script>
- 也可以用中括號:
<script>
const element = document.querySelector('.block2')
element.style['padding-top'] = '30px'
</script>
- 不過一般來說,不會將 style 直接寫在 script 裡面,因為這樣程式碼會很長,通常會寫在另一個 class 裡
改變元素的 class
.classList.add('')
- 新增 class,若要新增多個 class,可直接放在後面接 class 名稱,前面不需要加
.
("class1", "class2"...)
<head>
<style>
.decor {
background: blue;
}
</style>
</head>
<body>
<div class='block2'>
hello
</div>
<script>
const element = document.querySelector('.block2')
element.classList.add('decor')
</script>
</body>
.classList.remove('')
- 去掉元素 class
.classList.toggle('')
- 若元素原本有該 class 則 remove;原本無則 add
.classList.contains('')
- 判斷是否有這個 class,有的話 console.log 會印出 true
改變內容
element.innerText = ''
innerText
- 標籤中的文字內容
innerHTML
- 標籤裡面的所有內容
outerHTML
- 標籤和標籤裡所有的內容
插入與刪除元素
removeChild()
- 要刪除元素要知道 parent 是誰,所以要先找到母元素,再從母元素中 remove 子元素,也可以用
子元素.parentNode
來找到母元素
<body>
<div id="block">
Good morning
<a>My name is Apple</a>
<div id="block2">
hello
</div>
<div>
哩後
</div>
<script>
const element = document.querySelector("#block")
element.removeChild(document.querySelector('a'))
</script>
</body>
appendChild()
- 利用
document.createElement
新增標籤或document.createTextNode
新增文字
<script>
const element = document.querySelector("#block")
const item = document.createElement('div')
item.innerText = 'Stay calm'
element.appendChild(item)
</script>
<script>
const element = document.querySelector("#block3")
const item = document.createTextNode('Heyyyy')
element.appendChild(item)
</script>
JavaScript 網頁事件處理
addEventListener('<事件>', function)
- 事件監聽,針對事件做出反應
<script>
const element = document.querySelector("#block3")
element.addEventListener('click', onclick)
function onclick() {
alert('click')
}
</script>
- 或用匿名函式(和 callback function 無關係)
<script>
const element = document.querySelector("#block3")
element.addEventListener('click', function() {
alert('click!')
})
</script>
callback function
- 跟瀏覽器說,當⋯⋯時,幫我呼叫這個 function,幫我呼叫就是 callback 的意思。根據 MDN:回呼函式(callback function)是指能藉由參數(argument)通往另一個函式的函式。它會在外部函式內調用、以完成某些事情。
event(e)
- e 為瀏覽器所提供的參數(名稱可自己取),用 console.log(e) 可以印出事件的資訊。
<script>
const element = document.querySelector("#block3")
element.addEventListener('click', onclick)
function onclick(e) {
alert('click')
}
</script>
<body>
<div id="block3">
Good morning
<button class="change-btn1">點我</button>
</div>
<div id="block2">
hello
<a>My name is Apple</a>
</div>
<div>
哩後
</div>
<script>
const element = document.querySelector("#block3")
element.addEventListener('click', function(e) {
document.querySelector("#block3").classList.toggle("decor2")
})
</script>
- 用 console.log(e.key),可以印出自己點了哪個元素
<body>
<div id="block3">
Good morning
<button class="change-btn1">點我</button>
<input />
</div>
<script>
const element = document.querySelector("input")
element.addEventListener('keydown', function(e) {
console.log(e.key)
})
</script>
</body>
onSubmit
- 表單處理:在點擊 submit 之後,發生事件,通常用在表單驗證上面
範例,method 預設值為 GET:
<body> <form class="log-in" method="GET" Action=""> <div> username<input type="name" /> </div> <div> password<input name="password1" type="password" /> </div> <div> confirm password<input name="password2" type="password" /> </div> <div> <input name="submit" type="submit" value="送出" /> </div> </form> <script> const element = document.querySelector(".log-in") 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() } }) </script> </body>
e.preventDefault()
為阻止瀏覽器預設的行為,在這邊的話就是指「阻止表單被送出」的這個行為.value
可以拿到值
e.preventDefault()
- 範例,事件
keypress
按下按鍵:
<script>
const element = document.querySelector("input[name=username]")
element.addEventListener('keypress', function(e) {
if (e.key === 'e') {
e.preventDefault()
}
})
</script>
瀏覽器事件傳遞機制
事件發生的順序:
- 從 window 開始由上往下傳,這個 phase 為 capturing phase
- 到點擊的元素後,開始進入 target phase
- 再從點擊的元素由下往上傳,這個 phase 為 bubbling phase
addEventListner('<事件>', function, 'boolean')
布林值是true
,代表事件會在捕獲階段執行;布林值是false
,代表事件在冒泡階段執行;預設值為false
- 舊版的 Chrome,在進入 target phase 之後,冒泡和捕獲的觸發會根據
EventListner
的排序;新版的則更改了此行為,可參考此篇文章 e.preventDefault
會一直傳下去,所以整條鏈都會執行 preventDefault
e.stopPropagation
- 阻止事件繼續傳遞
<body>
<div class="outer">
outer
<div class="inner">
inner
<div class="btn">
<input class="btn" type="button" value="btn" />
</div>
</div>
</div>
<script>
const btn1 = document.querySelector(".outer")
btn1.addEventListener("click", function(e) {
e.stopPropagation()
console.log("click 1", "捕獲")
})
btn1.addEventListener("click", function(e) {
console.log("click 2", "捕獲")
})
</script>
</body>
e.stopImmediatePropagation
立即阻止事件傳遞
<body>
<div class="outer">
outer
<div class="inner">
inner
<div class="btn">
<input class="btn" type="button" value="btn" />
</div>
</div>
</div>
<script>
const btn1 = document.querySelector(".outer")
btn1.addEventListener("click", function(e) {
e.stopImmediatePropagation()
console.log("click 1", "捕獲")
})
btn1.addEventListener("click", function(e) {
console.log("click 2", "捕獲")
})
</script>
</body>
Event Delegation
- 須注意迴圈裡面變數的作用域
<body>
<div class="outer">
<button class="btn" data-value="1">1</button>
<button class="btn" data-value="2">2</button>
</div>
<script>
const elements = document.querySelectorAll(".btn")
for (let i = 0; i < elements.length; i++) {
elements[i].addEventListener("click", function() {
alert(i+1)
})
}
</script>
</body>
會顯示
- 如果宣告變數用
var
時,在 click 的時候會 alert 3,因為匿名函式是在點擊後才開始作用,而這時迴圈已經跑到最後一個 i 了
這樣的情況下可以用:
- 用
data-value
存資料,value 為屬性,是自訂的,所以 dash 後面放什麼都可以,例如,也可以更名為data-content
getAttribute()
可以拿到屬性
<body> <div class="outer"> <button class="btn" data-value="1">1</button> <button class="btn" data-value="2">2</button> </div> <script> 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")) }) } </script> </body>
- 用
新增按鈕
setAttribute("<屬性>", <值>)
:加屬性名稱、值.classList.add("<class 名稱>")
:加 class 名稱因為 EvenListner 是加在 1、2 按鈕上,所以後來動態新增的按鈕不會 alert 數字
<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> var 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("date-value", num) btn.classList.add("btn") btn.innerText = num num++ document.querySelector(".outer").appendChild(btn) }) </script>
利用冒泡的特性(不管有沒有加 even listener 都會持續冒泡),點擊最內層也會觸發到最外層,所以可直接在 outer 加
EventListner
,這就叫做 event delegation 「事件代理」
<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> var num = 3 document.querySelector(".add-btn").addEventListener("click", function() { const btn = document.createElement('button') btn.setAttribute("data-value", num) btn.classList.add("btn") btn.innerText = num num++ document.querySelector(".outer").appendChild(btn) }) document.querySelector(".outer").addEventListener("click", function(e) { if (e.target.classList.contains("btn")) { alert(e.target.getAttribute("data-value")) } } ) </script> </body>
實戰練習
密碼產生器
<head>
<meta charset='utf-8'>
<title>範例</title>
<meta name='viewport' content='width=device-width, initial-scale=1'/>
<style>
.app {
background: beige;
width: 700px;
height: 200px;
position: relative;
font-size: 32px;
}
.result {
background: springgreen;
width: 700px;
height: 100px;
position: absolute;
}
button {
width: 50px;
height: 50px;
background: pink;
}
</style>
</head>
<body>
<div class="app">
<div>
<input type="checkbox" id="en" /><label for="en">英文</label>
</div>
<div>
<input type="checkbox" id="number" /><label for="number">數字</label>
</div>
<div>
<input type="checkbox" id="symbol" /><label for="symbol">符號</label>
</div>
<button class="btn-generate">產生</button>
<div class="result"></div>
</div>
<script>
const element = document.querySelector(".btn-generate")
element.addEventListener("click",
function() {
let availableChar = ""
if (document.querySelector("#en").checked) {
availableChar += "abcdefghijklmnopqrstuvwxyz"
}
if (document.querySelector("#number").checked) {
availableChar += "0123456789"
}
if (document.querySelector("#symbol").checked) {
availableChar += "!@#$%^&()/"
}
let result = ""
for (let i = 0; i < 10; i++) {
num = Math.floor(Math.random() * availableChar.length)
result += availableChar[num]
}
document.querySelector(".result").innerText = result
}
)
</script>
</body>
- 也可以簡化用 function 簡化:
<script>
function getChar(id, char) {
if (document.querySelector(id).checked) {
return char
} else {
return " "
}
}
const element = document.querySelector(".btn-generate")
element.addEventListener("click",
function() {
let availableChar = ""
availableChar += getChar("#en", "abcdefghijklmnopqrstuvwxyz")
availableChar += getChar("#number", "0123456789")
availableChar += getChar("#symbol", "!@#$%^&()/")
let result = ""
for (let i = 0; i < 10; i++) {
num = Math.floor(Math.random() * availableChar.length)
result += availableChar[num]
}
document.querySelector(".result").innerText = result
}
)
</script>
動態表單通訊錄
document.querySelector(".contacts").addEventListener("click", .....
這邊利用事件代理,所以選.contacts
.closest()
:根據 MDN,closestElement is the Element which is the closest ancestor of the selected element.
<body>
<div class="app">
<div>
<button class="add">新增</button>
</div>
<div class="contacts">
<div class="row">
姓名:<input type="text" />
電話:<input type="text" />
<button class="delete">刪除</button>
</div>
</div>
</div>
<script>
document.querySelector(".add").addEventListener("click",
function() {
const parent = document.querySelector(".contacts")
const newRow = document.createElement("div")
newRow.classList.add("row")
newRow.innerHTML = `
姓名:<input type="text" />
電話:<input type="text" />
<button class="delete">刪除</button>
`
parent.appendChild(newRow)
}
)
document.querySelector(".contacts").addEventListener("click",
function(e) {
if (e.target.classList.contains("delete")) {
document.querySelector(".contacts").removeChild(
e.target.closest(".row")
)
}
})
</script>
</body>