使用 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全局对象