HTML5 History API 在在线运行器中必然失败,因 pushState 要求 HTTP/HTTPS 协议,而 JSBin 等使用 file:// 或沙盒 iframe,触发 SecurityError;唯一可靠方案是用 location.hash + hashchange,并注意首次加载补事件、正确解析 hash、委托绑定 data-nav 链接、用 class 切换视图而非 innerHTML,以及抽离可复用的路由解析逻辑。

在 HTML 在线运行器(如 JSBin、CodePen、JSFiddle 或本地 file:// 协议双击打开)中,history.pushState 会直接报错或被静默忽略 —— 这不是你代码写错了,是浏览器策略限制。必须用 location.hash 才能真正跑起来。
为什么 pushState 在在线运行器里一定失败
HTML5 History API 要求页面必须通过 HTTP/HTTPS 协议加载。在线运行器多数用沙盒 iframe 或 file:// 协议预览,此时调用 history.pushState 会抛出 SecurityError 或无反应。这不是兼容性问题,是硬性限制。
-
file://协议下所有pushState/replaceState调用均被禁止 - JSBin/CodePen 的预览 iframe 默认是
sandbox属性隔离,禁用 history 修改能力 - 即使看到 URL 变了(比如手动输
/about),刷新后 404,因为没有服务端 fallback 配置
hashchange 是唯一可靠的选择
它不依赖协议,不触发刷新,IE8+ 全支持,且在线运行器完全放行。关键是要绕过两个常见坑:
- 别只监听
hashchange就完事 —— 首次加载时事件不触发,得手动补一次:window.dispatchEvent(new Event('hashchange')) - 别直接拿
location.hash去===匹配,它带#,比如#/user;应统一用location.hash.slice(1)或location.hash.replace(/^#/, '')提取路径 - 跳转别用
location.href = '#/xxx',它会触发页面滚动到顶部;改用location.hash = '/xxx'(自动补#)或显式写location.hash = '#/xxx'
如何绑定点击并避免事件丢失
在线运行器里 DOM 经常被重载或沙盒重绘,用 document.addEventListener('click', ...) 委托容易失效。更稳的做法是:
立即学习“前端免费学习笔记(深入)”;
- 给每个导航
<a></a>加data-nav属性,例如:<a href="#/home" data-nav>首页</a> - 在脚本里遍历
document.querySelectorAll('[data-nav]'),逐个绑定click事件 - 在 handler 中调
e.preventDefault(),再设location.hash = e.target.getAttribute('href') - 不要用
href="#"+ JS 改内容 —— 用户手动删掉 hash 后退时,location.hash变空,但页面不会自动切回默认视图,得靠hashchange统一兜底
视图切换别反复 innerHTML
每次路由匹配都 document.getElementById('app').innerHTML = template,会导致已绑定的事件全部丢失(比如按钮 click 监听器)。尤其在线运行器里调试频繁,DOM 重建更频繁。
- 推荐用 CSS class 控制显隐:
.view { display: none; }+.view.active { display: block; } - 所有视图提前写死在 HTML 里,例如:
<div id="home" class="view active">...</div> - 在
hashchange回调里统一管理:document.querySelectorAll('.view').forEach(v => v.classList.remove('active')); document.querySelector(selector).classList.add('active'); - 如果某视图含动态逻辑(如表单提交),把它封装成独立函数,在
active加上后再手动调用初始化,而不是塞进模板字符串里
真正的难点不在“怎么切”,而在“状态怎么留”——比如从 #/post/123 切到 #/comments 再点后退,你得能还原出 123 并重新拉数据。这意味着解析逻辑不能只写一次,而要抽成函数,每次 hashchange 都调一遍,且结果要可序列化进 history.state(如果之后迁移到真实服务器环境)。在线运行器只是起点,别让它惯坏你的设计习惯。


















