轻量级 HTML5 History API 封装库,暴露全局对象 hjs。在现代浏览器上包装 history.replaceState,在不支持该 API 的旧浏览器上自动降级为 #! hash 路由,并提供统一的历史变化监听入口。
- 统一
replaceState调用入口 - 自动处理
data默认值({})与title默认值(当前document.title) - 旧浏览器 hash 降级(
#!path格式) - 防止重复初始化(
global.hjs守卫) - 内部维护状态列表,回调时可获取完整 state 对象
| 环境 | 行为 |
|---|---|
支持 history.replaceState |
使用原生 API + URL 轮询检测变化 |
不支持 history.replaceState |
通过 location.href = baseUrl + '#!' + path 模拟 |
| 变化检测 | 固定 100ms 轮询(非原生 popstate / hashchange 事件) |
通过 <script> 标签引入(当前无 npm 包):
<script src="history.js"></script>
<script>
hjs.replaceState({ page: 1 }, '首页', '/home');
hjs.bind('popstate', function(state) {
console.log(state.data, state.title, state.path);
});
</script>注意:
bind的第一个参数evtName当前未被实际使用,仅为占位;回调通过 100ms 轮询触发。
- 类型:
string - 当前值:
'1.0.0'
替换当前历史记录条目,等效于原生 history.replaceState 的封装版本。
参数
| 参数 | 类型 | 说明 |
|---|---|---|
data |
object |
状态对象,默认 {} |
title |
string |
页面标题,默认当前 document.title |
path |
string |
路由路径,必填,否则静默 return |
返回值
- 现代浏览器:返回递增的
subscribeId(内部索引) - 旧浏览器:无明确返回值(异步 hash 跳转)
副作用
- 更新
document.title - 将
{ target, data, title, path }写入内部dataList
注册历史变化监听。每 100ms 检测 URL 是否变化,变化时调用 hjs.fire(callback) 触发回调。
参数
| 参数 | 类型 | 说明 |
|---|---|---|
evtName |
string |
事件名(占位,当前未使用) |
callback |
function |
变化时的回调函数,接收 state 对象 |
已知限制:多次调用会创建多个独立的 setInterval,无法取消。
手动触发回调(内部方法,也可直接调用)。
- 现代浏览器:回调接收
dataList[subscribeId](当前最新状态) - 旧浏览器:遍历
dataList,对path与当前 hash 匹配的项逐一触发回调
{
target: window, // 全局对象
data: {}, // replaceState 传入的 data
title: '...', // 页面标题
path: '/xxx' // 路径
}// 替换当前历史记录
hjs.replaceState({ id: 1 }, '页面标题', '/page/1');
// 监听历史变化
hjs.bind('popstate', function(state) {
console.log('当前状态:', state);
console.log('数据:', state.data);
console.log('标题:', state.title);
console.log('路径:', state.path);
});hjs.replaceState('test', 'test title', '/2');- 仅实现了
replaceState,尚未实现pushState - 变化监听基于 100ms 轮询,存在性能开销与最多 100ms 的触发延迟
- 旧浏览器降级使用
#!(bang hash),而非常见的#/格式 - 代码中声明了
uri变量但未使用;parseUrl()已在源码中注释,尚未实现 - 无
package.json,当前仅支持 script 标签引入
以下功能可按优先级继续扩展:
| 扩展项 | 现状 | 建议 |
|---|---|---|
pushState |
未实现 | 参照 replaceState 实现,旧浏览器可用 hash 栈或 history 长度模拟 |
| 原生事件监听 | 使用 100ms 轮询 | 现代浏览器绑定 popstate;旧浏览器绑定 hashchange,轮询作兜底 |
go / back / forward |
未实现 | 封装 history.go(n),旧浏览器需维护内部 history 栈 |
| 扩展项 | 现状 | 建议 |
|---|---|---|
parseUrl() |
源码中已注释 | 恢复并暴露为 hjs.parseUrl(),解析 protocol / hostname / path / search / hash |
unbind / 单次监听 |
多次 bind 无法取消 |
返回 listener id,支持 off(id);避免重复 setInterval |
getState() |
无公开读取接口 | 暴露当前 dataList[subscribeId] 或从 history.state 读取 |
| 参数校验与错误提示 | path 无效时静默 return |
开发模式下 console.warn,生产模式保持兼容 |
| 扩展项 | 现状 | 建议 |
|---|---|---|
| 模块化导出 | 仅 IIFE 挂 window.hjs |
支持 UMD / ESM / CommonJS |
| TypeScript 类型 | 无 | 添加 hjs.d.ts |
| 单元测试 | 无 | 使用 jsdom + 测试框架覆盖现代 / 降级两条路径 |
| 构建与发布 | 无 package.json | 添加 npm 包、版本管理与 CI |
| hash 格式可配置 | 硬编码 #! |
支持 #/ 或自定义 prefix |
| 与前端路由框架集成 | 独立库 | 提供 React Router / Vue Router 适配示例 |
- 旧浏览器
fire逻辑:循环中可能对多个历史项重复触发回调,应只匹配当前 hash 最新项 bind内存泄漏:每次调用新建 interval,无清理机制replaceState旧浏览器分支:未返回subscribeId,与现代浏览器行为不一致
请查阅仓库根目录的 LICENSE 文件(如有)。