探究对 Vue Router 的 Hash 模式进行外部监听
Vue Router Hash 路由 hashchange 未实现Vue Router 的 Hash 模式,常见印象是「改 URL 里的 hash → 触发 hashchange → 切换页面组件」。但在真实场景里,给 window 挂上 hashchange 监听往往毫无反应。本文记录从现象到原因的排查过程,以及尚未落地的外部监听思路。
背景
需要在 iframe 父页面中感知子应用(Vue Router Hash 模式)的路由变化。直觉上监听 hashchange 即可,但实测回调从未触发。下文先复现问题,再分析根因。
复现
用 serve 起本地静态服务,暴露一个 HTML 文件即可快速复现:
给 window 加上 hashchange 监听:
无论怎么切换路由,控制台没有任何输出。

但在控制台手动输入 location.hash,又能看到 hash 确实在变。

原因分析
SegmentFault 上有一篇讨论与此现象一致:为什么 vue-router 的 hash 模式不触发 hashchange?
核心结论是:Vue Router 的 Hash 模式表面上在改 hash,实际上走的是 history.pushState / history.replaceState。这两个 API 可以在不触发 hashchange 的前提下更新地址栏里的 hash 片段。
因此,外部代码若只依赖原生 hashchange,就会和 Vue Router 的内部实现「对不上号」。
能否改听 pushState / replaceState?
既然路由跳转用的是 pushState 和 replaceState,直接监听这两个 API 行不行?
很遗憾,浏览器没有为它们提供对应的原生事件。原生只暴露了 popstate,且它只在用户点击后退/前进、或调用 history.back() 等场景下触发;pushState 和 replaceState 本身不会触发 popstate。
Vue Router 的做法是:在源码里重写 pushState / replaceState,挂上自己的监听器,从而在内部完成路由分发。下面这段 useHistoryListeners 可以看到它如何维护 listeners 数组,并在 popstate 时统一回调:
替代思路
若要在应用外部感知路由变化,更稳妥的方向包括:
- 在 Vue 应用内使用
router.afterEach等官方钩子,再通过postMessage等方式通知父页面(参见 Iframe 父子页面消息传递实战) - 在无法改子应用源码时,考虑轮询
location.hash、或同样 monkey-patchpushState/replaceState(需自行处理兼容与卸载)
小结
别被「Hash 模式」这个名字误导——它并不等价于浏览器的 hashchange 语义。目前我还没有找到一个既干净又通用的「纯外部监听」方案。如果你有更好的思路,欢迎交流。