使用 History API
History API 使網站能夠與瀏覽器會話歷史記錄進行互動,即使用者在給定視窗中訪問過的頁面列表。當用戶訪問新頁面時,例如透過單擊連結,這些新頁面將被新增到會話歷史記錄中。使用者還可以使用瀏覽器的“後退”和“前進”按鈕在歷史記錄中前後移動。
History API 中定義的主要介面是 History 介面,它定義了兩個截然不同的方法集:
-
導航到會話歷史記錄中頁面的方法
-
修改會話歷史記錄的方法
在本指南中,我們將僅介紹第二組方法。
pushState() 方法向會話歷史記錄新增新條目,而 replaceState() 方法則更新當前頁面的會話歷史記錄條目。這兩個方法都接受一個 state 引數,該引數可以包含任何 可序列化物件。當瀏覽器導航到此歷史記錄條目時,瀏覽器會觸發一個 popstate 事件,該事件包含與該條目關聯的狀態物件。
這些 API 的主要目的是支援 單頁應用程式 等網站,這些應用程式使用 fetch() 等 JavaScript API 來用新內容更新頁面,而不是載入整個新頁面。
單頁應用程式和會話歷史記錄
傳統上,網站是作為頁面集合實現的。當用戶透過單擊連結導航到網站的不同部分時,瀏覽器每次都會載入一個全新的頁面。
雖然這對許多網站都很棒,但它可能存在一些缺點:
- 每次都載入整個頁面可能效率低下,而實際上只需要更新頁面的一部分。
- 跨頁面導航時維護應用程式狀態很困難。
出於這些原因,Web 應用程式的一個流行模式是 單頁應用程式 (SPA)。當用戶單擊連結時,SPA 會執行以下步驟:
- 阻止載入新頁面的預設行為。
- 獲取要顯示的新內容。
- 使用新內容更新頁面。
例如
document.addEventListener("click", async (event) => {
const creature = event.target.getAttribute("data-creature");
if (creature) {
// Prevent a new page from loading
event.preventDefault();
try {
// Fetch new content
const response = await fetch(`creatures/${creature}.json`);
const result = await response.json();
// Update the page with the new content
displayContent(result);
} catch (err) {
console.error(err);
}
}
});
在此 click 處理程式中,如果連結包含資料屬性 "data-creature",那麼我們將使用該屬性的值來獲取包含頁面新內容的 JSON 檔案。
JSON 檔案可能如下所示:
{
"description": "Bald eagles are not actually bald.",
"image": {
"src": "images/eagle.jpg",
"alt": "A bald eagle"
},
"name": "Eagle"
}
我們的 displayContent() 函式使用 JSON 更新頁面。
// Update the page with the new content
function displayContent(content) {
document.title = `Creatures: ${content.name}`;
const description = document.querySelector("#description");
description.textContent = content.description;
const photo = document.querySelector("#photo");
photo.setAttribute("src", content.image.src);
photo.setAttribute("alt", content.image.alt);
}
問題是它破壞了瀏覽器“後退”和“前進”按鈕的預期行為。
從使用者的角度來看,他們單擊了一個連結,頁面更新了,所以它看起來像一個新頁面。如果他們然後按下瀏覽器的“後退”按鈕,他們期望回到單擊連結之前的狀態。
但就瀏覽器而言,最後一個連結沒有載入新頁面,所以“後退”會將瀏覽器帶到使用者開啟 SPA 之前載入的任何頁面。
這本質上是 pushState()、replaceState() 和 popstate 事件要解決的問題。它們使我們能夠合成歷史記錄條目,並在當前會話歷史記錄條目更改為其中一個條目時(例如,因為使用者按下了“後退”或“前進”按鈕)收到通知。
使用 pushState()
我們可以如下在上面的 click 處理程式中新增一個歷史記錄條目:
document.addEventListener("click", async (event) => {
const creature = event.target.getAttribute("data-creature");
if (creature) {
event.preventDefault();
try {
const response = await fetch(`creatures/${creature}.json`);
const result = await response.json();
displayContent(result);
// Add a new entry to the history.
// This simulates loading a new page.
history.pushState(result, "", creature);
} catch (err) {
console.error(err);
}
}
});
在這裡,我們使用三個引數呼叫 pushState():
使用 popstate 事件
假設使用者執行以下步驟:
- 單擊我們 SPA 中的連結,因此我們使用
pushState()更新頁面並新增歷史記錄條目 A。 - 單擊我們 SPA 中的另一個連結,因此我們使用
pushState()更新頁面並新增歷史記錄條目 B。 - 按下“後退”按鈕。
現在新的當前歷史記錄條目是 A,因此瀏覽器會觸發 popstate 事件,並且事件處理程式的引數包含我們在處理導航到 A 時傳遞給 pushState() 的 JSON。這意味著我們可以使用如下的事件處理程式恢復正確的內容:
// Handle forward/back buttons
window.addEventListener("popstate", (event) => {
// If a state has been provided, we have a "simulated" page
// and we update the current page.
if (event.state) {
// Simulate the loading of the previous page
displayContent(event.state);
}
});
使用 replaceState()
我們還需要新增最後一塊。當用戶載入 SPA 時,瀏覽器會新增一個歷史記錄條目。由於這是一個實際的頁面載入,因此該條目沒有關聯的狀態。所以假設使用者執行以下操作:
- 載入 SPA,因此瀏覽器會新增一個歷史記錄條目。
- 單擊 SPA 中的連結,因此 click 處理程式會更新頁面並使用
pushState()新增一個歷史記錄條目。 - 按下“後退”按鈕。
現在我們想返回 SPA 的初始狀態,但由於這是同一文件中的導航,頁面不會重新載入,並且由於初始頁面的歷史記錄條目沒有狀態,我們無法使用 popstate 來恢復它。
這裡的解決方案是使用 replaceState() 來設定初始頁面的狀態物件。例如:
// Create state on page load and replace the current history with it
const image = document.querySelector("#photo");
const initialState = {
description: document.querySelector("#description").textContent,
image: {
src: image.getAttribute("src"),
alt: image.getAttribute("alt"),
},
name: "Home",
};
history.replaceState(initialState, "", document.location.href);
在頁面載入時,我們會收集所有需要恢復的頁面部分,以便使用者返回 SPA 的起點。這具有與處理其他導航時獲取的 JSON 相同的結構。我們將此 initialState 物件傳遞給 replaceState(),這會有效地將狀態物件新增到當前歷史記錄條目。
當用戶返回我們的起點時,popstate 事件將包含此初始狀態,我們可以使用 displayContent() 函式來更新頁面。
完整的 History API 示例
您可以在 https://github.com/mdn/dom-examples/tree/main/history-api 找到此完整示例,並可以在 https://mdn.github.io/dom-examples/history-api/ 上檢視即時演示。
另見
- History API
history全域性物件