Vanilla JS单页应用路由切换后脚本无法运行的问题求助
兄弟,我太懂你这种头疼的情况了——自己手写Vanilla JS的SPA路由,经常会碰到这种「页面HTML渲染出来了,但脚本完全没生效」的问题!
问题根源
你现在的核心问题是:页面初始加载时,所有脚本(包括main.js)只会执行一次。当你通过路由切换到/about页面时,只是用innerHTML替换了页面内容,但那些依赖DOM元素的初始化逻辑(比如获取主题切换按钮、绑定点击事件)并没有重新运行——毕竟这些代码在首页加载时就已经执行过了,根本不知道新的DOM元素已经出现。
比如你的主题切换代码,在首页加载时会找到[data-theme-toggle]按钮并绑定事件,但切换到/about后,新的按钮是后来插入的,原来的代码早就跑完了,自然不会对这个新按钮生效。
解决方案
解决思路非常清晰:把需要在页面渲染后执行的脚本逻辑封装成独立函数,然后在每次路由切换完成(DOM更新后)手动调用这些函数。
第一步:把脚本逻辑封装成可重复调用的函数
修改你的main.js,把主题切换的所有代码打包成一个initThemeToggle函数,同时做一些小优化避免重复绑定事件:
function initThemeToggle() { // 先封装内部工具函数 function calculateSettingAsThemeString({ localStorageTheme, sessionStorageTheme, systemSettingDark }) { if (sessionStorageTheme !== null) { // 兼容Firefox跨页面localStorage的问题 return sessionStorageTheme; } if (localStorageTheme !== null) { return localStorageTheme; } return systemSettingDark.matches ? "dark" : "light"; } function updateButton({ buttonEl, isDark }) { const newCta = isDark ? "☼" : "☾"; buttonEl.setAttribute("aria-label", newCta); buttonEl.innerText = newCta; } function updateThemeOnHtmlEl({ theme }) { document.querySelector("html").setAttribute("data-theme", theme); } // 先检查目标元素是否存在,避免报错 const button = document.querySelector("[data-theme-toggle]"); if (!button) return; // 获取主题设置 const localStorageTheme = localStorage.getItem("theme"); const sessionStorageTheme = sessionStorage.getItem("theme"); const systemSettingDark = window.matchMedia("(prefers-color-scheme: dark)"); let currentThemeSetting = calculateSettingAsThemeString({ localStorageTheme, sessionStorageTheme, systemSettingDark }); // 初始化主题和按钮状态 updateButton({ buttonEl: button, isDark: currentThemeSetting === "dark" }); updateThemeOnHtmlEl({ theme: currentThemeSetting }); // 先移除旧的事件监听,避免重复绑定 button.removeEventListener("click", themeToggleHandler); // 定义点击事件处理函数 function themeToggleHandler() { currentThemeSetting = currentThemeSetting === "dark" ? "light" : "dark"; localStorage.setItem("theme", currentThemeSetting); sessionStorage.setItem("theme", currentThemeSetting); updateButton({ buttonEl: button, isDark: currentThemeSetting === "dark" }); updateThemeOnHtmlEl({ theme: currentThemeSetting }); } // 绑定新的事件监听 button.addEventListener("click", themeToggleHandler); } // 页面首次加载时先执行一次初始化 initThemeToggle();
第二步:在路由切换完成后调用初始化函数
修改router.js里的handleLocation函数,在更新完DOM内容之后,调用刚才封装的initThemeToggle函数:
const handleLocation = async () => { const path = window.location.pathname; const main = mainpage[path] || mainpage[404]; const main_html = await fetch(main).then((data) => data.text()); const sidesec = sidesection[path] || sidesection[404]; const sidesec_html = await fetch(sidesec).then((data) => data.text()); // 更新DOM内容 document.getElementById("main").innerHTML = main_html; document.getElementById("side").innerHTML = sidesec_html; // 关键:路由切换完成后,重新初始化页面脚本 initThemeToggle(); };
额外优化建议
如果你的main.js里还有其他功能(比如轮播、表单验证等),都要按照这个思路封装成独立的初始化函数,然后在handleLocation里统一调用。
另外,对于事件绑定,推荐使用事件委托的方式(把事件绑定到父容器或者document上),这样即使DOM更新,事件依然能生效,不用每次都重新绑定:
// 用事件委托替代直接绑定到按钮上 document.addEventListener("click", (e) => { if (e.target.matches("[data-theme-toggle]")) { // 这里直接写主题切换的逻辑,或者调用对应的处理函数 // 这样不管DOM怎么更新,只要按钮存在,点击就会生效 } });
这种方式会更省心,尤其当页面上有很多需要绑定事件的元素时。
备注:内容来源于stack exchange,提问作者Long Doan

