window.onhashchange有时不触发,因该事件仅在用户操作或history API调用时触发,而某些框架直接修改location.href或iOS Safari后台唤醒时存在兼容缺陷,需结合addEventListener、轮询、标记位等兜底方案。
window.onhashchange有时不触发直接给window绑定onhashchange看似合理,但实际中常失效——尤其在单页应用里手动调用location.hash = 'xxx'后没反应。根本原因是:该事件只在用户点击链接、浏览器前进/后退、或显式调用history.pushState/replaceState(配合hash)时触发,而某些框架(如早期Vue Router hash模式)会绕过原生事件机制,直接操作location.href字符串,导致onhashchange被跳过。
实操建议:
window.addEventListener('hashchange', handler)而非window.onhashchange = handler,避免被覆盖location.href = '#xxx'——这会重载页面或跳过事件,应改用location.hash = 'xxx'
handler,确保首屏路由状态不丢失iOS Safari(尤其14–15)存在一个经典bug:当页面从后台唤醒(比如切回微信内嵌页),hashchange可能完全静默,即使location.hash已变。这不是代码写错,是系统级限制。
实操建议:
setInterval每300ms读一次location.hash,和上一次缓存值比对if (currentHash !== lastHash) { handler(); lastHash = currentHash; }
visibilitychange事件里清空并重启轮询,应对页面可见性切换document.hidden的支持较晚,iOS 12.2+才稳定,旧版本需降级为pagehide/pageshow
hashchange事件对象里拿不到完整URL怎么办hashchange事件的event.oldURL和event.newURL在部分安卓WebView或旧版Chrome中为空,不能依赖它们解析路径。真正可靠的方式永远是读location.hash本身。
实操建议:
event.newURL,直接用location.hash.slice(1)取hash值(去掉#号)#user?id=1&tab=info),location.hash原样返回,无需拼接location.origin
location.hash始终含#开头,但URLSearchParams不支持解析带#的字符串,得先new URLSearchParams(location.hash.slice(1))
URLSearchParams,需用正则或第三方polyfill很多库会在router.push('/path')内部执行location.hash = '#/path',但如果业务层也监听了hashchange并再次调用相同逻辑,就会形成死循环。
实操建议:
isNavigatingByCode,在主动设置location.hash前设为true,在hashchange回调开头判断并return
setTimeout把标记位延迟清除,防止异步操作中状态错乱History API模拟(如history.replaceState({}, '', '#/path')),它不会触发hashchange,但能绕过标记位管理——不过要注意,这会让location.hash和地址栏显示不一致,调试困难hash路由监听真正的难点不在语法,而在各种运行时环境的撕裂感:iOS的休眠、安卓WebView的事件丢包、老浏览器的API缺失、框架对原生行为的覆盖……写死一个addEventListener远远不够,得随时准备用轮询、标记位、降级解析来补漏。