0x01 概述
- API 是一些預先定義的函式,提供應用程式與開發人員基於某軟體或硬體訪問一組例程的能力
- Web APIs 是 W3C 組織的標準,是 JS 獨有的部分
- Web API 是瀏覽器提供的一套操作瀏覽器功能和頁面元素的 API,即 DOM 與 BOM
0x02 DOM
(1)簡介
-
文件物件模型(Document Object Model)是 W3C 組織推薦的處理可擴充套件標記語言的標準程式設計介面
- 可以改變網頁內容、結構和樣式
-
DOM 樹:DOM 物件
graph TB 文件-->0[根元素<br/>< html >]-->1[元素<br/>< head >] & 2[元素<br/>< body >] 1-->11[元素<br/>< title >]-->111[文字<br/>文件標題] 2-->21[元素<br/>< a >] & 22[元素<br/>< h1 >] 21-->211[屬性<br/>href] & 212[文字<br/>連結文字] 22-->221[文字<br/>一級標題]- 文件:即頁面,DOM 中使用
document
表示 - 元素:頁面中的任何標籤,DOM 中使用
element
表示 - 節點:頁面中的任何內容,DOM 中使用
node
表示
- 文件:即頁面,DOM 中使用
(2)獲取元素
a. 根據 id
屬性
-
getElementById()
-
返回帶有指定
id
的元素物件
<p id="content">This is a paragraph.</p>
<script>
const pElement = document.getElementById("content");
console.dir(pElement);
</script>
b. 根據標籤名
-
getElementByTagName()
-
返回帶有指定標籤名的元素物件集合
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
<script>
const liElements = document.getElementsByTagName("li");
console.log(liElements);
</script>
-
也可以用於獲取父元素內所有指定標籤名的子元素
- 父元素必須為單個物件
<ul> <li>Item 1</li> <li>Item 2</li> <li>Item 3</li> </ul> <script> const ulElement = document.getElementsByTagName("ul"); const liElements = ulElement[0].getElementsByTagName("li"); console.log(liElements); </script>
c. 透過 HTML5 新方法
-
根據類名返回元素物件集合
getElementByClassName()
<div class="item">Item 1</div> <div class="item">Item 2</div> <div class="item">Item 3</div> <script> const divElements = document.getElementsByClassName("item"); console.log(divElements); </script>
-
根據指定選擇器返回第一個元素物件
querySelector()
<div class="item">Item 1</div> <div class="item">Item 2</div> <div class="item">Item 3</div> <script> const divElement = document.querySelector(".item"); console.log(divElement); </script>
-
根據指定選擇器返回元素物件集合
querySelectorAll()
<div class="item">Item 1</div> <div class="item">Item 2</div> <div class="item">Item 3</div> <script> const divElements = document.querySelectorAll(".item"); console.log(divElements); </script>
d. 特殊元素
-
html
元素<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Document</title> </head> <body> This is a simple page. <script> const htmlElement = document.documentElement; console.dir(htmlElement); </script> </body> </html>
-
body
元素<body> This is a simple page. <script> const bodyElement = document.body; console.dir(bodyElement); </script> </body>
(3)事件基礎
-
JavaScript 可用於建立動態頁面,事件是可以被 JavaScript 偵測到的行為
-
頁面中每個元素都可以產生某些可以觸發 JavaScript 的事件
<p>This is a paragraph.</p> <script> const pElement = document.querySelector("p"); pElement.onclick = function (e) { console.log(e); }; </script>
-
事件三要素包括:
- 事件源:事件被觸發的物件
- 事件型別:觸發事件的方式
- 事件處理:透過函式處理事件
-
執行事件的步驟:
- 獲取事件源
- 註冊(或繫結)事件
- 新增事件處理程式
- 採用函式賦值形式
-
常見滑鼠事件
事件 條件 onclick
左鍵點選後觸發 onmouseover
經過時觸發 onmouseout
離開時觸發 onfocus
獲得焦點觸發 onblur
失去焦點觸發 onmousemove
移動觸發 onmouseup
彈起觸發 onmousedown
按下觸發
(4)操作元素
- 使用 DOM 操作可以改變網頁內容、結構和樣式
a. 內容
innerHTML
:起始位置到終止位置的全部內容,包括 HTML 標籤、空格、換行等innerText
:類似innerHTML
,不包括 HTML 標籤、空格、換行等
<p>
This is a paragraph.<br />
<span>Text in span</span>
</p>
<script>
const pElement = document.querySelector("p");
console.log(pElement.innerHTML);
console.log(pElement.innerText);
</script>
b. 屬性
-
如
src
、href
、id
、value
等 -
舉例 1:圖片切換至下一張
<button id="next">下一張圖片</button> <img src="./1.png" alt="圖片1" /> <script> const next = document.getElementById("next"); const imgElement = document.querySelector("img"); next.onclick = () => { imgElement.src = "./2.png"; imgElement.alt = "圖片2"; next.disabled = true; }; </script>
-
舉例 2:顯隱密碼
<input type="password" name="password" autofocus /> <label>顯示密碼</label> <script> const label = document.querySelector("label"); const input = document.querySelector("input"); let flag = 1; label.onclick = () => { if (flag) { label.innerText = "隱藏密碼"; input.type = "text"; flag = 0; } else { label.innerText = "顯示密碼"; input.type = "password"; flag = 1; } }; </script>
c. 樣式
-
行內樣式操作
<div style="width: 300px; height: 300px; background-color: red"></div> <script> const div = document.querySelector("div"); div.onclick = () => { div.style.backgroundColor = "blue"; }; </script>
- JavaScript 中,樣式採取駝峰命名法,如
backgroundColor
等 - JavaScript 修改樣式操作產生的是行內樣式,CSS 的權重更高
- JavaScript 中,樣式採取駝峰命名法,如
-
類名樣式操作
<div style="width: 300px; height: 300px; background-color: red"></div> <script> const div = document.querySelector("div"); div.onclick = () => { div.className = "clicked"; }; </script>
className
會更改(覆蓋)元素的類名
-
舉例 1:密碼框格式錯誤提示資訊
<div> <input type="password" /> <p>輸入 3~10 個字元</p> </div> <script> const input = document.querySelector("input") const p = document.querySelector("p"); input.onblur = function() { if (this.value.length < 3 || this.value.length > 10) { p.className = 'error' p.innerText = "字元數錯誤" } else { p.className = 'right' p.innerText = "字元數正確" } } </script>
-
舉例 2:(排他思想)
<button>按鈕 1</button> <button>按鈕 2</button> <button>按鈕 3</button> <script> const buttons = document.querySelectorAll("button"); for (let i = 0; i < buttons.length; i++) { buttons[i].onclick = function () { for (let j = 0; j < buttons.length; j++) { buttons[j].style.backgroundColor = ""; } this.style.backgroundColor = "red"; }; } </script>
-
舉例 3:表格行在滑鼠懸浮時換色
<table border="1"> <thead> <tr> <th>Name</th> <th>Age</th> <th>Gender</th> </tr> </thead> <tbody> <tr> <td>John</td> <td>20</td> <td>Male</td> </tr> <tr> <td>Jane</td> <td>21</td> <td>Female</td> </tr> <tr> <td>Jim</td> <td>22</td> <td>Male</td> </tr> </tbody> </table> <script> const trs = document.querySelector("tbody").querySelectorAll("tr"); for (let i = 0; i < trs.length; i++) { trs[i].onmouseover = function () { this.style.backgroundColor = "red"; }; trs[i].onmouseout = function () { this.style.backgroundColor = ""; }; } </script>
-
舉例 4:表單全選的選中與取消
<table border="1"> <thead> <tr> <th><input type="checkbox" id="selectAll" /></th> <th>Name</th> <th>Age</th> </tr> </thead> <tbody> <tr> <td><input type="checkbox" /></td> <td>John</td> <td>20</td> </tr> <tr> <td><input type="checkbox" /></td> <td>Jane</td> <td>21</td> </tr> <tr> <td><input type="checkbox" /></td> <td>Jim</td> <td>22</td> </tr> </tbody> </table> <script> const selectAll = document.getElementById("selectAll"); const checkboxes = document.querySelector("tbody").querySelectorAll("input"); selectAll.onchange = function () { checkboxes.forEach(function (checkbox) { checkbox.checked = selectAll.checked; }); }; for (let i = 0; i < checkboxes.length; i++) { checkboxes[i].onchange = function () { if (!this.checked) { selectAll.checked = false; } else { let allChecked = true; checkboxes.forEach(function (checkbox) { if (!checkbox.checked) { allChecked = false; } }); selectAll.checked = allChecked; } }; } </script>
d. 自定義屬性
element.屬性
獲取的是內建屬性(即元素本身自帶的屬性)element.getAttribute('屬性')
獲取的是自定義和內建的屬性
<div></div>
<script>
const div = document.querySelector("div");
div.setAttribute("data-tabindex", 1); // 設定屬性
const tabindex = div.getAttribute("data-tabindex"); // 獲取屬性
console.log(tabindex);
div.removeAttribute("data-tabindex"); // 移除屬性
</script>
-
自定義屬性的目的:儲存並使用資料
- 對於可以簡單且可以明文展示的資料可以儲存在頁面中,省去使用資料庫
-
HTML5 規定,自定義屬性使用
data-
字首命名並賦值 -
HTML5 新增以下方法獲取屬性值:
<div data-class-name="div"></div> <script> const div = document.querySelector("div"); console.log(div.dataset.className); // 方法一 console.log(div.dataset["className"]); // 方法二 </script>
(5)節點操作
a. 簡介
-
節點操作主要是利用節點層級關係獲取元素
- 利用父子兄節點關係獲取元素
- 邏輯性強
- 相容性差
-
節點是頁面中的任何內容,DOM 中使用
node
表示,均可使用 JavaScript 訪問 -
一般地,節點至少擁有以下基本屬性:
-
節點型別:
nodeType
節點型別 nodeType
值HTML 元素 1 屬性 2 文字 3 -
節點名稱:
nodeName
-
節點值:
nodeValue
-
b. 父節點
<div id="parent">
<div id="child"></div>
</div>
<script>
const parent = document.getElementById("child").parentNode;
console.log(parent);
</script>
c. 子節點
-
獲取子節點集合,包括元素節點、文字節點等
<div id="parent"> <div id="child1"></div> <div id="child2"></div> <div id="child3"></div> </div> <script> const children = document.getElementById("parent").childNodes; console.log(children); </script>
-
只獲取子節點集合中的元素節點
const children = document.getElementById("parent").childNodes; for (let i = 0; i < children.length; i++) { if (children[i].nodeType === 1) { console.log(children[i]); } }
或
const children = document.getElementById("parent").children; console.log(children);
-
第一個子節點
const first = document.getElementById("parent").firstChild; console.log(first);
第一個元素子節點
-
推薦方法
const first = document.getElementById("parent").children[0]; console.log(first);
-
存在相容性問題,需要 IE9+
const first = document.getElementById("parent").firstElementChild; console.log(first);
-
-
最後一個子節點
const last = document.getElementById("parent").lastChild; console.log(last);
最後一個元素子節點
-
推薦方法
const parent = document.getElementById("parent"); const last = parent.children[parent.children.length - 1]; console.log(last);
-
存在相容性問題,需要 IE9+
const last = document.getElementById("parent").lastElementChild; console.log(last);
-
-
舉例:導航欄及其下拉選單
<ul id="nav"> <li> Item 1 <ul style="display: none"> <li>Subitem 1</li> <li>Subitem 2</li> </ul> </li> <li> Item 2 <ul style="display: none"> <li>Subitem 1</li> <li>Subitem 2</li> </ul> </li> </ul> <script> const nav = document.body.children[0]; const items = nav.children; for (let i = 0; i < items.length; i++) { items[i].onmouseover = function () { this.children[0].style.display = "block"; }; items[i].onmouseout = function () { this.children[0].style.display = "none"; }; } </script>
d. 兄弟節點
-
獲取當前元素的下一個兄弟節點
<h1>Title</h1> <p>This is a paragraph.</p> <script> const p = document.querySelector("h1").nextSibling; console.log(p); </script>
-
元素節點
const p = document.querySelector("h1").nextElementSibling; console.log(p);
-
-
獲取當前元素的上一個兄弟節點
<h1>Title</h1> <p>This is a paragraph.</p> <script> const h1 = document.querySelector("p").previousSibling; console.log(h1); </script>
-
元素節點
const h1 = document.querySelector("p").previousElementSibling; console.log(h1);
-
-
上述獲取兄弟元素節點的方法,均存在相容性問題,需要 IE9+,為解決此問題,可以封裝以下方法:
function getNextElementSibling(element) { let next = element.nextSibling; while (next && next.nodeType !== 1) { next = next.nextSibling; } return next; } function getPreviousElementSibling(element) { let prev = element.previousSibling; while (prev && prev.nodeType !== 1) { prev = prev.previousSibling; } return prev; }
e. 建立與新增節點
-
動態建立元素節點:
createElement()
const p = document.createElement("p");
-
新增節點至指定父節點的子節點的末尾:
appendChild()
p.innerText = "This is a paragraph"; // 設定標籤內容 document.body.appendChild(p); // 新增節點
-
在指定元素前面插入元素:
insertBefore()
const h1 = document.createElement("h1"); h1.innerText = "Title"; document.body.insertBefore(h1, p);
-
舉例:釋出留言
<textarea></textarea> <button>釋出</button> <ul></ul> <script> const btn = document.querySelector("button"); btn.onclick = function () { const text = document.querySelector("textarea"); if (text) { const ul = document.querySelector("ul"); const li = document.createElement("li"); li.innerHTML = text.value; // ul.appendChild(li); ul.insertBefore(li, ul.firstChild) } else { alert("釋出內容不能為空"); } }; </script>
-
直接將內容寫入頁面的文件流:
write()
<script> document.write("<button>點選</button>"); const btn = document.querySelector("button"); btn.onclick = () => document.write("<p>This is a paragraph.</p>"); </script>
-
write()
、innerHTML()
、createElement()
三種方法區別:write()
在當文件流執行完成後,會導致頁面重繪innerHTML()
是將內容寫入某個 DOM 節點,適合建立多個元素,結構稍複雜createElement()
結構清晰,效率較低
f. 刪除節點
-
刪除一個節點:
removeChild()
<p>This is a paragraph.</p> <button>刪除節點</button> <script> document.querySelector("button").onclick = () => document.body.removeChild(document.querySelector("p")); </script>
-
舉例 1:刪除留言(在“釋出留言”案例的基礎上修改)
const btn = document.querySelector("button"); btn.onclick = function () { const text = document.querySelector("textarea"); if (text) { const ul = document.querySelector("ul"); const li = document.createElement("li"); li.innerHTML = text.value + "<a href='javascript:;'>刪除</a>"; ul.insertBefore(li, ul.firstChild); const as = document.querySelectorAll("a"); for (let i = 0; i < as.length; i++) { as[i].onclick = function () { // this.parentNode.remove(); ul.removeChild(this.parentNode); }; } } else { alert("釋出內容不能為空"); } };
-
舉例 2:動態表格
<table border="1"> <thead> <tr> <th>姓名</th> <th>年齡</th> <th>操作</th> </tr> </thead> <tbody></tbody> </table> <script> let data = [ { name: "張三", age: 24 }, { name: "李四", age: 22 }, { name: "王五", age: 26 }, { name: "趙六", age: 21 }, ]; const tbody = document.querySelector("tbody"); for (let i = 0; i < data.length; i++) { const tr = document.createElement("tr"); for (const key in data[i]) { const td = document.createElement("td"); td.innerText = data[i][key]; tr.appendChild(td); } const td = document.createElement("td"); td.innerHTML = "<a href='javascript:;'>刪除</a>"; tr.appendChild(td); tbody.appendChild(tr); } const as = document.querySelectorAll("a"); for (let i = 0; i < data.length; i++) { as[i].onclick = function () { tbody.removeChild(as[i].parentNode.parentNode); }; } </script>
g. 複製節點
-
克隆一個節點:
cloneNode()
<p>This is a paragraph.</p> <script> const p = document.querySelector("p").cloneNode(true); document.body.appendChild(p); </script>
-
其中,
cloneNode()
的引數預設為false
,即淺複製,不會複製子節點
0x03 事件高階
(1)註冊事件
a. 簡介
- 註冊事件又稱繫結事件,給元素新增事件
- 註冊事件方式包括:
- 傳統方式
- 以
on
為字首的事件,如onclick
、onchange
等 - 註冊事件的唯一性,即同一元素同一事件只能設定一個處理函式,最後註冊的處理函式會覆蓋前面註冊的
- 以
- 方法監聽註冊方式
- 採用 W3C 標準
- 使用
addEventListener()
監聽 - IE9 之前可以使用
attachEvent()
代替 - 同一元素同一事件可以註冊多個監聽器,並按註冊順序執行
- 傳統方式
b. addEventListener
-
將指定的監視器註冊到目標物件上,當該物件觸發指定事件時,就會執行事件處理函式
-
語法:
addEventListener(type, listener[, useCapture])
-
type
:事件型別字串,如click
、change
等 -
listener
:事件處理函式,即監聽函式 -
useCapture
:(可選)是否在捕獲階段觸發,是布林值,預設false
“事件捕獲”在本章節第(3)節說明
<button>點選</button> <script> document.querySelector("button").addEventListener("click", () => { alert("觸發點選事件"); }); </script>
-
c. attachEvent
- 將指定的監視器註冊到目標物件上,當該物件觸發指定事件時,就會執行指定的回撥函式
- 語法:
attachEvent(eventNameWiteOn, callback)
eventNameWithOn
:事件型別字串,如onclick
、onchange
callback
:回撥函式,用於事件處理
d. 相容性解決方案
相容性處理原則:首先照顧大多數瀏覽器,再處理特殊瀏覽器
function addEventListener(element, eventName, callback) {
if (element.addEventListener) {
element.addEventListener(eventName, eventName);
} else if (element.attachEvent) {
element.attachEvent(eventName, callback);
} else {
element["on" + eventName] = callback;
}
}
(2)刪除事件
-
刪除事件又稱解綁事件
-
刪除事件方式包括:
-
傳統方式
<button>點選</button> <script> document.querySelector("button").onclick = null; </script>
-
方法監聽刪除方式
-
使用
removeEventListener()
刪除<button>點選</button> <script> const btn = document.querySelector("button"); function clickEvent() { alert("觸發點選事件"); btn.removeEventListener("click", clickEvent); } btn.addEventListener("click", clickEvent); </script>
-
IE9 之前可以使用
detachEvent()
代替
-
-
-
相容性解決方案
function removeEventListener(element, eventName, callback) { if (element.removeEventListener) { element.removeEventListener(eventName, callback); } else if (element.attachEvent) { element.detachEvent("on" + eventName, callback); } else { element["on" + eventName] = null; } }
(3)DOM 事件流
-
事件流描述的是從頁面中接收事件的順序
-
DOM 事件流:事件發生時會在元素節點之間按照特定順序的傳播過程
-
DOM 事件流分三個階段:
- 捕獲階段
- 事件捕獲:由 DOM 最頂層節點開始,逐級向下傳播到最具體的元素接收的過程
- 當前目標階段
- 冒泡階段
- 事件冒泡:事件開始時,由最具體的元素接收,之後逐級向上傳播到 DOM 最頂層節點的過程
- 捕獲階段
-
舉例:註冊事件採取捕獲階段觸發,先父元素節點,後子元素節點
<div id="parent" style="width: 200px; height: 200px; background: red"> <div id="child" style="width: 100px; height: 100px; background: green"></div> </div> <script> document.getElementById("parent").addEventListener( "click", () => { alert("parent"); }, true ); document.getElementById("child").addEventListener( "click", () => { alert("child"); }, true ); </script>
-
部分事件沒有冒泡,如
onblur
等
(4)事件物件
-
事件物件代表事件的狀態,是事件一系列相關資料的集合,包括滑鼠座標等資料
-
在以下程式碼中,形參
event
就是事件物件<button>點選</button> <script> document.querySelector("button").onclick = function (event) { console.log(event); }; </script>
-
IE6~8 使用
window.event
寫法,相容性寫法為:e = event || window.event;
-
常見屬性和方法
屬性方法 說明 target
返回觸發事件的物件 srcElement
返回觸發事件的物件(在 IE6~8 中使用) type
返回事件型別,如 click 等 stopPropagation()
阻止冒泡 cancelBubble
阻止冒泡(在 IE6~8 中使用) preventDefault()
阻止預設事件 returnValue
阻止預設事件(在 IE6~8 中使用) -
阻止事件冒泡的相容性寫法
if (e & e.stopPropagation) { e.stopPropagation(); } else { window.event.cancelBubble = true; }
(5)事件委託
-
事件委託又稱事件代理,在 jQuery 中稱為事件委派
-
原理:將每個子節點的事件監聽器統一設定在其父節點上,利用冒泡原理影響每個子節點
-
作用:提高效能
-
舉例:
<ul> <li>Item 1</li> <li>Item 2</li> <li>Item 3</li> <li>Item 4</li> <li>Item 5</li> <li>Item 6</li> </ul> <script> document.querySelector("ul").addEventListener("mouseover", function (e) { e.target.style.backgroundColor = "red"; }); </script>
(6)常用滑鼠事件
-
禁用右鍵選單
document.addEventListener("contextmenu", function (e) { e.preventDefault(); });
-
禁止滑鼠選中
document.addEventListener("selectstart", function (e) { e.preventDefault(); });
-
舉例:跟隨滑鼠移動的方框
<div style=" width: 100px; height: 100px; background-color: red; position: absolute; transform: translate(-50%, -50%); " ></div> <script> const div = document.querySelector("div"); document.addEventListener("mousemove", (e) => { div.style.left = e.pageX + "px"; div.style.top = e.pageY + "px"; }); </script>
-
mouseenter
與mouseover
的區別在於:mouseenter
不會冒泡mouseenter
只在滑鼠經過盒子自身時觸發,而mouseover
經過其子盒子也會觸發
<div style=" width: 200px; height: 200px; background-color: red; position: relative " > <div style=" width: 100px; height: 100px; background-color: green; position: absolute; top: 50px; left: 50px; " ></div> </div> <script> const parent = document.querySelector("div"); const child = document.querySelector("div div"); parent.addEventListener("mouseenter", function () { console.log("mouseenter"); }); parent.addEventListener("mouseover", function () { console.log("mouseover"); }); </script>
(7)常用鍵盤事件
-
常用鍵盤事件
事件 說明 keyup 按鍵鬆開時觸發 keydown 按鍵按下時觸發 keypress 按鍵按下時觸發(區別大小寫,不識別功能鍵,如 Ctrl 等) keyCode 返回按鍵的 ASCII 值(區分大小寫) -
按鍵執行順序:
graph LR keydown-->keypress-->keyup -
舉例:按
/
鍵選中輸入框<input /> <script> const input = document.querySelector("input"); document.addEventListener("keyup", function (e) { if (e.keyCode === 191) { input.focus(); } }); </script>
0x04 BOM
(1)簡介
-
瀏覽器物件模型(Browser Object Model)提供了獨立於內容而與瀏覽器視窗進行互動的物件
-
BOM 由一系列相關物件構成
-
BOM 缺乏標準,最初是 Netscape 瀏覽器標準的一部分
-
與 DOM 對比:
DOM BOM 名稱 文件物件模型 瀏覽器物件模型 物件 文件 瀏覽器 頂級物件 document
window
功能 操作頁面元素 瀏覽器視窗互動 標準 W3C 根據瀏覽器廠商 -
BOM 構成:
graph TB window-->document & location & navigation & screen & history- document:BOM 中包含 DOM
- window:是瀏覽器的頂級物件,既是 JS 訪問瀏覽器視窗的介面,也是全域性物件
- 呼叫 window 物件方法時可以省略,如
alert()
,除了window.name
- 呼叫 window 物件方法時可以省略,如
(2)window 物件常見事件
-
視窗載入完成事件
window.onload = function () {}; // 或 window.addEventListener("load", function () {});
-
DOM 載入完成事件(需要 IE9+)
window.addEventListener("DOMContentLoaded", function () {});
-
-
視窗大小調整事件
window.onresize = function () {}; // 或 window.addEventListener("resize", function () {});
(3)定時器
a. timeout
-
設定計時器
-
語法:
setTimeout(callback[, timeMS])
-
作用:經過指定毫秒後執行回撥函式
-
-
清除定時器
- 語法:
clearTimeout(timer)
- 作用:清除
setTimeout()
設定的定時器
- 語法:
-
舉例:
let timer = setTimeout(function() {}, 1000); clearTimeout(timer);
b. interval
-
設定計時器
-
語法:
setInterval(callback[, timeMS])
-
作用:間隔指定毫秒後重複執行回撥函式
-
舉例:倒數計時器
<div> <span class="hour">00</span> <span class="minute">00</span> <span class="second">00</span> </div> <script> const hour = document.querySelector(".hour"); const minute = document.querySelector(".minute"); const second = document.querySelector(".second"); function countDown() { const now = new Date(); console.log(now); const target = new Date(2024, 1, 1, 0, 0, 0); const diff = target - now; hour.innerHTML = Math.floor(diff / 1000 / 60 / 60); minute.innerHTML = Math.floor((diff / 1000 / 60) % 60); second.innerHTML = Math.floor((diff / 1000) % 60); } setInterval(countDown, 500); </script>
-
-
清除定時器
- 語法:
clearInterval(timer)
- 作用:清除
setInterval()
設定的定時器
- 語法:
-
舉例:傳送簡訊間隔
手機號: <input type="number" /><button>獲取驗證碼</button> <script> document.querySelector("button").addEventListener("click", callback); function callback() { let phone = document.querySelector("input").value; if (!/^1[3-9]\d{9}$/.test(phone)) { alert("手機號格式不正確"); return; } this.disabled = true; let time = 10; let timer = setInterval(() => { this.innerHTML = `重新傳送(${time})`; time--; if (time === 0) { clearInterval(timer); this.innerHTML = "獲取驗證碼"; this.disabled = false; time = 10; } }, 1000); } </script>
(4)執行佇列
a. 執行過程
-
JavaScript 的特點是單執行緒,即同一時間只做一件事
- 缺點:當 JS 執行時間過長,會導致頁面渲染不連貫,產生阻塞的感受
-
HTML5 提出 Web Worker 標準,允許 JavaScript 指令碼建立多個執行緒,即同步與非同步
- 同步:順序依次執行
- 非同步:多工同時執行
-
同步任務都在主執行緒上執行,形成執行棧
-
非同步任務透過回撥函式實現,分為以下型別:
- 普通事件:如
click
等 - 資源載入:如
load
等 - 定時器
非同步任務相關回撥函式會被新增到任務佇列(又稱訊息佇列)中
- 普通事件:如
b. 執行機制
- JavaScript 執行機制如下:
- 先執行執行棧中的同步任務
- 將非同步任務放入任務佇列
- 完成執行棧後,按次序讀取任務佇列,將非同步任務放入執行棧並執行
- 由於主執行緒不斷地重複獲取任務、執行任務,因此該機制稱為事件迴圈
(5)location 物件
-
用於獲取或設定窗體的 URL,並且可以用於解析 URL
-
屬性:
屬性 說明 href
獲取或設定 URL host
返回主機(域名) port
返回埠號 pathname
返回路徑 search
返回引數 hash
返回片段 -
舉例:獲取 URL 中攜帶的引數
<div></div> <script> const params = location.search.substr(1); const array = params.split("="); document.querySelector("div").innerHTML = array[1]; </script>
-
-
常用方法:
方法 說明 assign()
重定向頁面,與屬性 href 類似,可以跳轉頁面 replace()
替換當前頁面,替換後不能後退 reload()
重新載入頁面,相當於重新整理或 F5
引數為true
表示強制重新整理或 Ctrl+F5
(6)navigator 物件
-
包含有關瀏覽器的資訊
-
舉例:根據終端(瀏覽器)的不同跳轉相應的頁面
if((navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|iPad|ios|Android|BlackBerry|IEMobile|Opera Mini|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i))) { window.location.href = "https://m.example.com/"; // Mobile } else { window.location.href = "https://www.example.com/"; // PC }
(7)history 物件
-
用於與瀏覽器歷史記錄進行互動,包含使用者(在瀏覽器視窗中)訪問過的 URL
-
方法:
方法 說明 back()
後退 forward()
前進 go(arg)
引數為正數:前進 arg
個頁面
引數為負數:後退arg
個頁面 -
常見於 OA 辦公系統
0x05 PC 端網頁特效
(1)元素偏移量 offset
a. 簡介
-
動態獲取元素的位置(偏移)、大小等
- 獲取元素距離帶有定位父元素的位置
- 獲取元素自身的大小(寬高)
- 獲取的數值不攜帶單位
-
常用屬性:
屬性 說明 offsetParent
返回作為該元素帶有定位的父級元素 offsetTop
返回元素相對帶有定位父元素上方的偏移 offsetLeft
返回元素相對帶有定位父元素左邊框的偏移 offsetWidth
返回自身包括內邊距、邊框、內容的寬度 offsetHeight
返回自身包括內邊距、邊框、內容的高度 -
相比之下,
offset
更適合獲取元素的大小與位置,而style
更適合修改元素
b. 獲取滑鼠在盒子中的座標
<div style="width: 200px; height: 200px; background-color: red"></div>
<script>
document.querySelector("div").addEventListener("mousemove", function (e) {
let x = e.pageX - this.offsetLeft;
let y = e.pageY - this.offsetTop;
this.innerText = `x: ${x}, y: ${y}`;
});
</script>
c. 拖動模態框
-
HTML
<button id="open">點選登入</button> <div id="mask"></div> <div id="card"> <h3 id="header">登入框標題</h3> <button id="close">關閉</button> </div>
-
CSS
#mask { width: 100%; height: 100%; position: fixed; top: 0; left: 0; background-color: rgba(0, 0, 0, 0.3); display: none; } #card { width: 400px; height: 300px; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 10; background-color: white; border-radius: 30px; box-shadow: 0 0 10px rgb(0, 0, 0); text-align: center; line-height: 40px; display: none; } #header { width: 100%; margin-top: 0; border-bottom: 2px solid rgba(0, 0, 0, 0.3); cursor: move; }
-
JavaScript
const mask = document.querySelector("#mask"); const card = document.querySelector("#card"); const header = document.querySelector("#header"); document.querySelector("#open").addEventListener("click", () => { mask.style.display = "block"; card.style.display = "block"; }); document.querySelector("#close").addEventListener("click", () => { mask.style.display = "none"; card.style.display = "none"; }); header.addEventListener("mousedown", function (e) { e.preventDefault(); let x = e.clientX - card.offsetLeft; let y = e.clientY - card.offsetTop; function move(e) { card.style.left = e.pageX - x + "px"; card.style.top = e.pageY - y + "px"; } document.addEventListener("mousemove", move); document.addEventListener("mouseup", function (e) { document.removeEventListener("mousemove", move); }); });
d. 圖片放大鏡
-
HTML
<div id="image"> <div id="mask"></div> <div id="preview"> <img src="./image.jpg" alt="" id="previewImg" /> </div> </div>
-
CSS
#image { position: relative; width: 200px; height: 200px; background-image: url("./image.jpg"); background-size: cover; border: 1px solid black; } #mask { position: absolute; top: 0; left: 0; width: 100px; height: 100px; background-color: red; opacity: 0.5; cursor: move; display: none; } #preview { position: absolute; top: 0; left: 210px; width: 300px; height: 300px; overflow: hidden; z-index: 10; border: 1px solid black; display: none; } #previewImg { position: absolute; top: 0; left: 0; }
-
JavaScript
const image = document.querySelector("#image"); const mask = document.querySelector("#mask"); image.addEventListener("mouseover", () => { mask.style.display = "block"; preview.style.display = "block"; }); image.addEventListener("mouseout", () => { mask.style.display = "none"; preview.style.display = "none"; }); image.addEventListener("mousemove", function (e) { let x = e.pageX - this.offsetLeft; let y = e.pageY - this.offsetTop; let maskX = x - mask.offsetWidth / 2; let maskY = y - mask.offsetHeight / 2; let maskMaxX = this.offsetWidth - mask.offsetWidth; let maskMaxY = this.offsetHeight - mask.offsetHeight; if (maskX < 0) { maskX = 0; } else if (maskX > maskMaxX) { maskX = this.offsetWidth - mask.offsetWidth; } if (maskY < 0) { maskY = 0; } else if (maskY > maskMaxY) { maskY = this.offsetHeight - mask.offsetHeight; } mask.style.left = maskX + "px"; mask.style.top = maskY + "px"; const preview = document.querySelector("#preview"); const previewImg = document.querySelector("#previewImg"); let previewMaxX = previewImg.offsetWidth - preview.offsetWidth; let previewMaxY = previewImg.offsetHeight - preview.offsetHeight; previewImg.style.left = -((maskX * previewMaxX) / maskMaxX) + "px"; previewImg.style.top = -((maskY * previewMaxY) / maskMaxY) + "px"; });
(2)元素可視區 client
-
獲取元素可視區相關資訊,如邊框大小、元素大小等
-
常見屬性:
屬性 說明 clientTop
返回元素上邊框的大小 clientLeft
返回元素左邊框的大小 clientWidth
返回自身包括內邊距、內容的寬度 clientHeight
返回自身包括內邊距、內容的高度
立即執行函式:
(function() {})()
主要作用:建立一個獨立的作用域
(3)元素滾動 scroll
-
獲取元素的大小、滾動距離等
-
如果瀏覽器的高度(寬度)不足以顯示全部內容,則會自動出現捲軸
- 當捲軸向下(向右)滾動後,未顯示在當前視窗的頁面稱為被捲去的頁面
- 捲軸在滾動時會觸發
onscroll
事件
-
常見屬性:
屬性 說明 scrollTop
返回被捲去的上側距離 scrollLeft
返回被捲去的左側距離 scrollWidth
返回自身內容的寬度 scrollHeight
返回自身內容的高度 -
被捲去的頭部相容性寫法
function getScroll() { return { left: window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0, top: window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0, }; }
-
舉例:側邊欄
-
HTML
<header></header> <div id="content"></div> <div id="bar"> <button>回到頂部</button> </div>
-
CSS
header { width: 90%; height: 200px; background-color: #ccc; margin: 10px auto; } #content { width: 90%; height: 1000px; background-color: #eee; margin: 10px auto; } #bar { position: absolute; top: 300px; right: 0; width: 30px; height: 150px; background-color: #ddd; } button { display: none; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }
-
JavaScript
const bar = document.querySelector("#bar"); const btn = document.querySelector("button"); let ctnTop = document.querySelector("#content").offsetTop; let barTop = bar.offsetTop - ctnTop; document.addEventListener("scroll", () => { if (window.scrollY > ctnTop) { bar.style.position = "fixed"; bar.style.top = barTop + "px"; btn.style.display = "block"; } else { bar.style.position = "absolute"; bar.style.top = "300px"; btn.style.display = "none"; } }); btn.addEventListener("click", () => { window.scrollTo({ top: 0, behavior: "smooth", }); });
-
(4)動畫函式封裝
-
原理:透過定時器
setInterval()
不斷移動盒子的位置- 盒子需要定位才能使用動畫
<button>開始</button> <div style=" width: 200px; height: 200px; background-color: red; position: absolute; left: 0; " ></div> <script> document.querySelector("button").addEventListener("click", () => { const div = document.querySelector("div"); let timer = setInterval(() => { if (div.offsetLeft >= 400) { clearInterval(timer); } div.style.left = div.offsetLeft + 1 + "px"; }, 25); }); </script>
-
動畫函式的簡單封裝需要傳遞兩個引數:動畫物件、移動距離
function animate(obj, target) { clearInterval(obj.timer); obj.timer = setInterval(() => { if (obj.offsetLeft >= target) { clearInterval(obj.timer); } obj.style.left = obj.offsetLeft + 1 + "px"; }, 25); } document.querySelector("button").addEventListener("click", () => { animate(document.querySelector("div"), 500); });
-
緩動動畫是讓元素的運動速度發生變化(即,將移動距離遞減)
- 步長需要取整
- 正步長向上取整,負步長向下取整
function animate(obj, target) { clearInterval(obj.timer); obj.timer = setInterval(() => { if (obj.offsetLeft === target) { clearInterval(obj.timer); } let step = (target - obj.offsetLeft) / 50; step = step > 0 ? Math.ceil(step) : Math.floor(step); obj.style.left = obj.offsetLeft + step + "px"; }, 25); }
-
在動畫函式中引入回撥函式
function animate(obj, target, callback) { clearInterval(obj.timer); obj.timer = setInterval(() => { if (obj.offsetLeft === target) { clearInterval(obj.timer); if (callback) callback(); } let step = (target - obj.offsetLeft) / 50; step = step > 0 ? Math.ceil(step) : Math.floor(step); obj.style.left = obj.offsetLeft + step + "px"; }, 25); } document.querySelector("button").addEventListener("click", () => { animate(document.querySelector("div"), 500, () => { alert("移動結束"); }); });
-
將動畫函式封裝到單獨的 JavaScript 檔案中
-
建立 animate.js
/** * 實現元素的平滑移動動畫。 * @param {Object} obj - 需要進行動畫的DOM物件。 * @param {number} target - 目標位置,即元素移動到的左偏移量。 * @param {Function} callback - 動畫完成後的回撥函式。 */ function animate(obj, target, callback) { // 停止當前正在進行的動畫 clearInterval(obj.timer); // 設定定時器,每25毫秒執行一次動畫邏輯 obj.timer = setInterval(() => { // 當元素移動到目標位置時,停止動畫 if (obj.offsetLeft === target) { clearInterval(obj.timer); // 如果設定了回撥函式,則動畫完成後執行回撥 if (callback) callback(); } // 計算每次移動的距離,平滑移動 let step = (target - obj.offsetLeft) / 50; // 確保移動方向正確,且移動步長為整數 step = step > 0 ? Math.ceil(step) : Math.floor(step); // 更新元素的左偏移量 obj.style.left = obj.offsetLeft + step + "px"; }, 25); }
-
在頁面中使用
<script src="./animate.js"></script> <script> document.querySelector("button").addEventListener("click", () => { animate(document.querySelector("div"), 500, () => { alert("移動結束"); }); }); </script>
-
0x06 本地儲存
- 本地儲存特性:
- 資料儲存在使用者瀏覽器中
- 儲存與讀寫方便
- 容量大(
sessionStorage
- 5M,localStorage
- 20M) - 只能儲存字串,可以將物件使用
JSON.stringify()
編碼後儲存
(1)sessionStorage
-
當瀏覽器視窗關閉後,
sessionStorage
生命週期終止 -
在同一視窗(頁面)下,資料可以共享
-
以鍵值對的形式儲存和使用
-
舉例:
<input type="text" /> <button id="save">儲存</button> <button id="read">讀取</button> <button id="delete">刪除</button> <button id="clear">清空</button> <script> let input = document.querySelector("input"); document.querySelector("#save").addEventListener("click", () => { sessionStorage.setItem("value", input.value); alert("儲存成功"); }); document.querySelector("#read").addEventListener("click", () => { let value = sessionStorage.getItem("value"); alert(`讀取內容: ${value}`); }); document.querySelector("#delete").addEventListener("click", () => { sessionStorage.removeItem("value"); alert("刪除成功"); }); document.querySelector("#clear").addEventListener("click", () => { sessionStorage.clear(); alert("清空成功"); }); </script>
(2)localStorage
-
生命週期永久有效,只能手動刪除
-
在同一瀏覽器下,資料可以共享
-
以鍵值對的形式儲存和使用
-
舉例:
<input type="text" /> <button id="save">儲存</button> <button id="read">讀取</button> <button id="delete">刪除</button> <button id="clear">清空</button> <script> let input = document.querySelector("input"); document.querySelector("#save").addEventListener("click", () => { localStorage.setItem("value", input.value); alert("儲存成功"); }); document.querySelector("#read").addEventListener("click", () => { value = localStorage.getItem("value"); alert(`讀取內容: ${value}`); }); document.querySelector("#delete").addEventListener("click", () => { localStorage.removeItem("value"); alert("刪除成功"); }); document.querySelector("#clear").addEventListener("click", () => { localStorage.clear(); alert("清空成功"); }); </script>
(3)記住使用者名稱
<input type="text" id="username" />
<input type="checkbox" id="rememberMe" />
<label for="rememberMe">記住我</label>
<script>
const username = document.querySelector("#username");
const rememberMe = document.querySelector("#rememberMe");
if (localStorage.getItem("username")) {
username.value = localStorage.getItem("username");
rememberMe.checked = true;
}
rememberMe.addEventListener("change", function () {
if (this.checked) {
localStorage.setItem("username", username.value);
} else {
localStorage.removeItem("username");
}
});
</script>