Javascript 在不同執行環境執行下的注意事項
* 假設一個 function setTimeout 在瀏覽器與 Node.js 上都能使用,但他並不是 JS 這個語言本身就有的 function,且雖然兩個執行環境 runtime 都能使用 setTimeout,且看起來一樣;使用方式也一樣,但兩個執行環境下實作這個 function 的方式分別是不同的。
JavaScript 與瀏覽器的溝通
執行 JavaScript
在 html 中引入額外的 js 檔案
<script src="./index.js"></script>
文件物件模型 DOM(Document Object Model)
- 把文件(html 裡面寫的東西)轉換成物件,瀏覽器提供這個橋樑讓我們可以利用 Javascript 來改變網頁的內容。
- DOM 提供了文件以擁有屬性與函式的節點與物件組成的結構化表示,總之就是把 html 的文件內容轉換成有階層關係(各種節點)的類似物件的形式。節點也可以附加事件處理程序,一旦觸發事件就會執行處理程序。
MDN 文件物件模型 (DOM)
好用網站
如何選到想要的元素
<script src="./index.js"></script>
<!--這段通常會放在 </body> 之前,因為程式是由上往下依序執行,
放在 <head> 的話會抓不到後面才設定的東西-->
const elements = document.getElementsByTagName("h2")
const elements = document.getElementsByClassName("title")
const elements = document.getElementById("yo")
const elements = document.querySelector(".title")
/* 裡面直接用 Css 的選擇器的語法即可,.title + div 這類的語法也可以使用 */
const elements = document.querySelectorAll("div")
/* 沒有加 all 的話只會選到第一個元素 */
- 特別加映:
input
標籤內的元素用querySelector
選取:
input[type='']
、input[name='']
,如果名稱是變數,要用+
隔開才選得到。<div><label><input type="checkbox" name="en" />英文</label></div> <div><label><input type="checkbox" name="num" />數字</label></div> <div><label><input type="checkbox" name="symbol" />特殊符號</label></div>
function getChar(name, char) {
if (document.querySelector('input[name=' + name + ']').checked) {
return char
}
return
}
改變元素的 Css
const elements = document.querySelector(".title")
elements.style.padding = "10px"
elements.style['padding-top'] = "10px"
elements.style.paddingTop = "10px"
// 較不常這樣直接改
改變元素的 Class
// 在 html 先建立好一個叫 .active 的 style
const elements = document.querySelector("#block")
elements.classList.add('active')/* 對 block 裡的元素新增 class */
elements.classList.remove('active') /* 移除 class */
elements.classList.toggle('active') /* 開關,原本有的話會移除,沒有的話會新增 */
elements.classList.contains('active')/*尋找 block 這層的 class 中有沒有包含 active 這個 class*/
改變內容:inner、outer 的 HTML 與 text
const elements = document.querySelector("#block")
elements.innerText/* 抓取 block 元素內的所有文字 */
elements.innerHTML/* 抓取 block 元素內包含 HTML 標籤的所有文字 */
elements.outerHTML/* 抓取 block 元素本身與內部包含 HTML 標籤的所有文字 */
插入與刪除元素:appendChild 與 removeChild
elements.removeChild(document.querySelector("a")) /* 移除選取的元素 */
const item = document.createElement('div')
/* 要新增前先設置元素,createElement 新增 HTML 標籤*/
//也可以直接 remove()
var el = document.getElementById('div-02');
el.remove(); // Removes the div with the 'div-02' id
const item = document.createTextNode('123') /* 也可以直接新增純文字 */
item.innerText = '123' /* 將文字放入標籤 */
elements.appendChild(item) /* 新增元素 */
註:
document.querySelector(".list__area").appendChild(div);
document.querySelector(".list__area2").appendChild(div);
// 在同一層 node 節點使用 appendChild 插入 div 並不會複製到兩個都有,只會移動,
// 所以此時 div 只會從 .list__area 移動到 .list__area2 上面,所以只有後者會顯示 div
JavaScript 網頁事件處理
eventListener 與 callback function
==event listener 事件監聽==: 當設定的事件被出發時,後面接要執行的 function。
==callback function 回呼函式==: 不需要持續監聽等待 click 這個動作發生,因為不知道什麼時候會被按按鈕,可以先處理其他東西;而當 click 這個動作發生時,瀏覽器幫忙呼叫設定的 function 然後再執行就好了。
const elements = document.querySelector("#block")
elements.addEventListener('click', function() {}, false)
/* click 為要監聽(發生)的事件,function 就是當事件發生時要執行的動作,
此時這個無名 function 是匿名函式,
執行 function 這個動作就是瀏覽器幫忙 callback function 的結果 */
- 第三個參數 false 代表將監聽事件加在下往上的冒泡階段(預設),true 代表將監聽事件加在上往下的捕獲階段,所以若想控制事件監聽的順序可以使用此方式,簡單來說就是可以選擇在哪邊裝上監聽事件,但注意==兩者皆不會改變事件傳遞的順序==。
event(e) 是啥?
const elements = document.querySelector("#block")
elements.addEventListener('click', function(e) {})
// 瀏覽器在 callback function 的時候回傳的變數,裡面有發生事件的資訊,
// 例如按了哪個位置,打了什麼字等等
console.log(e.target)
// 會取出觸發 callback function 的時候所做的行為的位置,例如點了哪個區塊等等。
console.log(e.currentTarget)
// 取出 EventListener 的位置
表單事件處理 onSubmit
const elements = document.querySelector('.login-form')
elements.addEventListener('submit', function(e) {
alert('submit!')
e.preventDefault() // 阻止瀏覽器的預設行為,此情況是送出表單
})
- ==preventDefault==: 阻止瀏覽器的預設行為,常見用在阻止表單送出(密碼打錯之類)與阻止連到超連結。而 preventDefault 跟 JavaScript 的事件傳遞「一點關係都沒有」,你加上這一行之後,事件還是會繼續往下傳遞。
事件傳遞機制
- DOM 的事件在傳播時,會先從根節點開始往下傳遞到target,這邊你如果加上事件的話,就會處於CAPTURING_PHASE,捕獲階段。事件傳遞這個機制無論如何都會發生。
- target就是你所點擊的那個目標,這時候在target身上所加的eventListenr會是AT_TARGET這一個 Phase。
- 最後,事件再往上從子節點一路逆向傳回去根節點,這時候就叫做BUBBLING_PHASE,也是大家比較熟知的冒泡階段。
- 關於事件的傳遞順序,只要記住兩個原則就好:
- 先捕獲,再冒泡
- 當事件傳到 target 本身,沒有分捕獲跟冒泡
- ==stopPropagation==: 阻止事件傳遞,之後的階段事件都不會觸發。這邊指的「事件傳遞被阻止」,意思是說不會再把事件傳遞給「下一個節點」,但若是你在同一個節點上有不只一個 listener,還是會被執行到。
- ==stopImmediatePropagation==: 阻止「所有」的事件傳遞。
- 假設有3層 html 標籤,由內往外層的關係分別是 'in', 'middle', 'out',當使用者點擊 'in' 區塊時,事件會從最外層的視窗 windows > 'out' > 'middle' > 'in' 這樣依序傳遞到事件的目標(target) 'in',以上階段是捕獲(capturing)。然後事件會再從目標 'in' 往上依序傳遞回 windows,此階段是冒泡(bubbling)。
事件代理機制 event delegation ==超重要==
- 利用冒泡的特性來對上層元素設定執行的事件,就可以套用到下層的元素,此時上層就是事件代理。
- 例如今天有一個
ul
,底下 1000 個li
,如果你幫每一個li
都加上一個eventListener
,你就要建立 1000 個 function。但依據冒泡特性,任何點擊li
的事件其實都會傳到ul
身上,因此我們可以在ul
身上掛一個 listener 就好。這樣透過父節點來處理子節點的事件,就叫做事件代理。 - 假設一個父節點下面有很多個子節點,而當我們要對子節點設置監聽事件的時候,因為子節點太多所以要設很多個很麻煩沒效率,而且動態新增元素的時候也抓取不到,所以可以利用這個機制與事件傳遞的捕獲冒泡特性來對上面的父節點來設置監聽,就可以確保能監聽到底下所有子節點的事件。
- 動態事件常用,例如待辦清單的新增事件與刪除,掛在父節點的話才能確保動態新增與刪除的元素都可以被抓取到。(範例:week7 hw3)
```javascript=
e.target.parentNode //找 target 的上層節點
e.target.closest("css selector")
//找到距離 target 最近的設定目標祖先(上層),也可以是 target 本身。
// 忘記可估狗 "js vanilla find parent with class"
function closest(node, className) {
while(node && node.classList) {
if (node.classList.contains(className)) {
// 尋找 node 的 classList 有沒有包含目標 className
return node
}
node = node.parentNode // 持續往上層找
}
}
```
參考資料:尋找父子節點
如何在瀏覽器上儲存資料?
- ==Cookie==: 類型為「小型文字檔案」,指某些網站為了辨別用戶身分而儲存在用戶端(Client Side)上的資料(通常經過加密),會自動帶入 server。維基百科
- Local Storage: 在瀏覽器內儲存資訊,與伺服器無關。
- Session Storage: 與 local storage 類似,但只存在分頁中,關掉與開其他分頁資訊就不見,用於短暫的資訊儲存。