history对象保存了浏览器的历史记录,提供了导航功能。
// 历史记录长度
console.log(history.length); // 历史记录数量
// 滚动恢复行为
console.log(history.scrollRestoration); // "auto" 或 "manual"
// 当前状态
console.log(history.state); // pushState设置的状态对象
// 自动恢复滚动位置(默认)
history.scrollRestoration = "auto";
// 手动管理滚动位置
history.scrollRestoration = "manual";
window.addEventListener("load", function() {
// 手动恢复滚动位置
const scrollY = sessionStorage.getItem("scrollY");
if (scrollY) {
window.scrollTo(0, parseInt(scrollY));
}
});
window.addEventListener("beforeunload", function() {
sessionStorage.setItem("scrollY", window.scrollY);
});
在历史记录中前进和后退。
// 后退一步
history.back();
// 前进一步
history.forward();
// 移动指定步数
history.go(-1); // 后退一步(等同于back)
history.go(1); // 前进一步(等同于forward)
history.go(-2); // 后退两步
history.go(2); // 前进两步
history.go(0); // 刷新当前页面
// 检查是否可以前进/后退
// 没有直接API,但可以尝试
function canGoBack() {
return history.length > 1;
}
// 后退按钮
const backButton = document.querySelector("#back");
backButton.addEventListener("click", function() {
if (history.length > 1) {
history.back();
} else {
location.href = "/";
}
});
// 前进按钮
const forwardButton = document.querySelector("#forward");
forwardButton.addEventListener("click", function() {
history.forward();
});
添加新的历史记录条目。
// history.pushState(state, title, url)
// 添加历史记录
history.pushState(
{ page: 1 }, // 状态对象
"Page 1", // 标题(目前被忽略)
"/page/1" // URL
);
// 相对URL
history.pushState({ page: 2 }, "", "page/2");
// 完整URL(必须同源)
history.pushState({ page: 3 }, "", "/page/3");
// 查询字符串
history.pushState({ page: 4 }, "", "?page=4");
// Hash
history.pushState({ section: "intro" }, "", "#intro");
// 状态对象可以是任何可序列化的值
history.pushState({ page: 1, sort: "name" }, "", "?page=1&sort=name");
// 获取状态
console.log(history.state); // { page: 1, sort: "name" }
// 复杂状态
history.pushState({
user: { id: 1, name: "东巴文" },
filters: { status: "active", role: "admin" },
timestamp: Date.now()
}, "", "/users/1");
// 简单的SPA路由
class Router {
constructor() {
this.routes = {};
this.init();
}
init() {
// 监听popstate事件
window.addEventListener("popstate", (e) => {
this.handleRoute(location.pathname);
});
// 处理初始路由
this.handleRoute(location.pathname);
}
register(path, handler) {
this.routes[path] = handler;
}
navigate(path, state = {}) {
history.pushState(state, "", path);
this.handleRoute(path);
}
handleRoute(path) {
const handler = this.routes[path] || this.routes["/404"];
if (handler) {
handler(history.state);
}
}
}
// 使用
const router = new Router();
router.register("/", () => showHomePage());
router.register("/about", () => showAboutPage());
router.register("/users", () => showUsersPage());
// 导航
router.navigate("/about");
替换当前历史记录条目。
// history.replaceState(state, title, url)
// 替换当前历史记录
history.replaceState(
{ page: 1 },
"",
"/page/1"
);
// 与pushState的区别
// pushState: 添加新记录,可以后退
// replaceState: 替换当前记录,不能后退到之前
// 场景1:登录后替换URL
// 登录页URL: /login?return=/dashboard
function handleLoginSuccess() {
const returnUrl = new URLSearchParams(location.search).get("return") || "/";
history.replaceState({}, "", returnUrl);
// 用户不能后退到登录页
}
// 场景2:清理URL参数
// URL: /search?q=js&page=1
function cleanUrl() {
const url = new URL(location.href);
url.searchParams.delete("temp");
history.replaceState(history.state, "", url);
}
// 场景3:更新状态但不添加历史
function updatePage(page) {
const url = new URL(location.href);
url.searchParams.set("page", page);
history.replaceState({ page }, "", url);
loadData(page);
}
历史记录变化时触发的事件。
window.addEventListener("popstate", function(e) {
console.log("历史记录变化");
console.log("状态:", e.state);
console.log("当前URL:", location.href);
// 根据状态更新页面
if (e.state && e.state.page) {
showPage(e.state.page);
}
});
// popstate在以下情况触发:
// 1. 点击浏览器后退按钮
// 2. 点击浏览器前进按钮
// 3. history.back()
// 4. history.forward()
// 5. history.go()
// popstate不会在以下情况触发:
// 1. history.pushState()
// 2. history.replaceState()
// 3. 页面加载
window.addEventListener("popstate", function(e) {
console.log("popstate触发");
});
history.pushState({}, "", "/new"); // 不触发
history.back(); // 触发
class SPARouter {
constructor() {
this.routes = new Map();
this.currentRoute = null;
this.init();
}
init() {
// 监听popstate
window.addEventListener("popstate", (e) => {
this.dispatch(location.pathname, e.state);
});
// 监听链接点击
document.addEventListener("click", (e) => {
const link = e.target.closest("a[href]");
if (link && this.isInternalLink(link)) {
e.preventDefault();
this.navigate(link.getAttribute("href"));
}
});
// 初始路由
this.dispatch(location.pathname, history.state);
}
isInternalLink(link) {
return link.hostname === location.hostname &&
!link.getAttribute("href").startsWith("#") &&
!link.getAttribute("target");
}
route(path, handler) {
this.routes.set(path, handler);
return this;
}
navigate(path, state = {}) {
history.pushState(state, "", path);
this.dispatch(path, state);
}
replace(path, state = {}) {
history.replaceState(state, "", path);
this.dispatch(path, state);
}
dispatch(path, state) {
const handler = this.routes.get(path);
if (handler) {
handler(state);
this.currentRoute = path;
} else {
// 404处理
const notFound = this.routes.get("*");
if (notFound) notFound(state);
}
}
}
// 使用
const router = new SPARouter();
router
.route("/", () => renderHome())
.route("/about", () => renderAbout())
.route("/users", (state) => renderUsers(state))
.route("*", () => render404());
// 导航
router.navigate("/users", { page: 1 });
实际开发中的应用场景。
// Tab切换
const tabs = document.querySelectorAll(".tab");
const contents = document.querySelectorAll(".content");
tabs.forEach(tab => {
tab.addEventListener("click", function() {
const id = this.dataset.tab;
// 更新UI
tabs.forEach(t => t.classList.remove("active"));
contents.forEach(c => c.classList.remove("active"));
this.classList.add("active");
document.getElementById(id).classList.add("active");
// 更新URL
history.pushState({ tab: id }, "", `#${id}`);
});
});
// 恢复状态
window.addEventListener("popstate", function(e) {
if (e.state && e.state.tab) {
const tab = document.querySelector(`[data-tab="${e.state.tab}"]`);
if (tab) tab.click();
}
});
// 初始化
if (location.hash) {
const tab = document.querySelector(`[data-tab="${location.hash.slice(1)}"]`);
if (tab) tab.click();
}
// 分页
function loadPage(page) {
fetch(`/api/data?page=${page}`)
.then(res => res.json())
.then(data => {
renderData(data);
// 更新URL
const url = new URL(location.href);
url.searchParams.set("page", page);
history.pushState({ page }, "", url);
});
}
// 监听后退/前进
window.addEventListener("popstate", function(e) {
if (e.state && e.state.page) {
loadPage(e.state.page);
}
});
// 初始化
const currentPage = new URLSearchParams(location.search).get("page") || 1;
loadPage(currentPage);
// 保存表单状态
const form = document.querySelector("form");
function saveFormState() {
const formData = new FormData(form);
const state = Object.fromEntries(formData);
history.replaceState({ form: state }, "");
}
// 输入时保存
form.addEventListener("input", debounce(saveFormState, 500));
// 恢复表单状态
function restoreFormState(state) {
if (state && state.form) {
Object.entries(state.form).forEach(([name, value]) => {
const field = form.elements[name];
if (field) field.value = value;
});
}
}
// 页面加载时恢复
restoreFormState(history.state);
// 后退时恢复
window.addEventListener("popstate", function(e) {
restoreFormState(e.state);
});
// 维护导航历史
const navigationHistory = [];
function navigate(path, title) {
navigationHistory.push({
path: location.pathname,
title: document.title
});
history.pushState({ history: navigationHistory }, "", path);
document.title = title;
updateBreadcrumbs();
}
function updateBreadcrumbs() {
const breadcrumbs = document.querySelector(".breadcrumbs");
const history = history.state?.history || [];
breadcrumbs.innerHTML = history
.map(item => `<a href="${item.path}">${item.title}</a>`)
.join(" > ");
}
掌握了History对象后,让我们继续学习:
东巴文(db-w.cn) - 让编程学习更简单
🎯 东巴文寄语:History API是构建单页应用(SPA)的核心技术,掌握pushState、replaceState和popstate事件,可以实现无刷新页面导航。在 db-w.cn,我们帮你深入理解前端路由原理!