[Javascript] History API

history 是浏览器环境中所支持的一个对象,该对象用于管理当前创建最近访问过的 URL 历史记录,所有的 URL 会被存储在一个名为 histroy 的对象里面,回头就可以通过 JS 脚本调用 history 对象的方法从而控制浏览器前进或者后退。

例如打开浏览器,新创建一个标签页会话,然后在控制台输入 history,那么就能够看到这个对象

16929493237023
  • length:表示历史记录堆栈中 URL 的数量,这包括了当前页面以及之前访问过的页面的记录。需要注意,这个属性是一个只读属性,不能够通过代码去修改的。

  • scrollRestoration:对应的值有两个

    • auto:默认值,回到上一个历史记录的时候,浏览器会尝试自动滚动到上一次所滚动的地方
    • manual:需要开发者自己来处理这个滚动恢复
  • state:这个属性表示当前历史记录条目的 state 状态对象,这个状态对象的值可以由 pushState 或者 repalceState 来创建,如果没有通过这两个方法进行创建的话,那么默认值为 null

之所以要复习这个 history 对象,是因为我们的单页应用主要就是和这个 history 对象打交道。

所谓单页应用,本质上只有一张 html 页面,要做到视图的变化,就需要需要前端路由去映射不同的模块。

目前比较流行的单页方式有两种:

  • hash 模式
  • html5 模式

hash模式

hash 模式的原理在于一个 URL 的 hash 部分的变化,不会引起和服务器之间进行交互,那么我们就可以让 hash 部分的值和视图模块相对应:

example.com/#/index  // 首页视图
example.com/#/list   // 列表视图

hash 的值的改变不会导致向服务器发送请求,但是 history 列表是会发生改变,那么用户就可以通过浏览器的前进和后退去控制视图的变化,开发者也可以通过 history 提供的相关方法 forward、back、go 来进行不同视图之间的跳转。

hash 改变之后,会触发 hashchange 的事件,所以我们一般会去监听这个事件,从而根据 hash 值的变化来进行视图的切换:

window.addEventListener('hashchange', function() {
  // 获取新的哈希值
  const hash = window.location.hash;

  // 根据新的哈希值来更新页面内容
  switch (hash) {
    case '#/page1':
      // 加载 page1 的内容
      break;
    case '#/page2':
      // 加载 page2 的内容
      break;
    // 更多的情况...
  }
});

html5模式

html5模式用到 history 相关的 API 来通过另外一种方式实现这个单页应用:

pushState:用于在浏览器堆栈里面添加一个新的状态,这个方法接收 3 个参数

history.pushState(state, title, url)
  • state:要推入的状态对象,一般要求这个对象就是一个纯粹的数据对象(能够被 JSON.stringify 转为字符串的对象)
  • title:新页面的标题,一般浏览器都会忽略这个参数,所以一般传递一个空字符串或者 null 即可
  • url:新的历史记录的 url

例如,我们在控制台输入如下的代码:

history.pushState({page:'page1'}, '', '/page1')

假设浏览器的 url 原本是 www.test.com 的话,那么执行了上面的代码之后,就会变化 www.test.com/page1 ,并且不会和服务器之间进行通信,但是 history 堆栈会发生响应的变化,这就为我们通过这种方式来实现单页应用创建了良好的条件。

replaceState:该方法和 pushState 方法是类似的,但是不会推入新的状态,而是替换。

history.repalceState({page:'page1'}, '', '/page1')

popState:当浏览器页面进行前进后退的时候,会触发 popState 事件,你可以监听这个事件,然后从事件对象中的获取之前传递给 pushState 或者 repalceState 的 state 状态对象。

window.addEventListener('popstate', function(event) {
  console.log('state: ', event.state);
});

下面我们来举一个例子,下面是基于 html5 history api 所实现的一个简单单页应用:

<div id="app">
  <h1>My Single Page App</h1>
  <div>
    <a href="/" onclick="navigate(event, '/')">Home</a> |
    <a href="/about" onclick="navigate(event, '/about')">About</a>
  </div>
  <div id="content"></div>
</div>
// 渲染函数
function render(state) {
  const content = document.getElementById('content');

  switch(state) {
    case '/':
      content.innerHTML = '<h2>Welcome to Home Page</h2>';
      break;
    case '/about':
      content.innerHTML = '<h2>About Page</h2>';
      break;
    default:
      content.innerHTML = '<h2>Page Not Found</h2>';
  }
}

// 导航函数:保证用户点击页面中的 a 标签的时候,能够进行正常跳转
function navigate(e, route) {
  e.preventDefault(); // 阻止默认事件
  history.pushState(route, '', route); // 推入新的状态
  render(route); // 渲染对应的视图
}

// 监听 popstate 事件
// 保证用户点击浏览器本身的前进后退按钮的时候,视图能够进行切换
window.addEventListener('popstate', function(e) {
  render(e.state);
});

// 初始化
document.addEventListener('DOMContentLoaded', function() {
  const url = window.location.pathname;
  history.replaceState(url, '', url);
  render(url);
});

html5 模式相比 hash 模式,外观上更加的简洁美观,没有 # 号了,但是也有一个缺点,就是当你刷新页面的时候,会发现服务器会返回 404,原因很简单,例如前面我们所举的例子,URL 变为了 www.test.com/page1 ,刷新的时候会将整个这个 URL 提交给服务器,但是服务器后端无法找到对应的资源,所以会报错。

为了解决这个问题,就需要我们在服务器上面做一些配置,使得针对所有的路由请求,服务器都返回一份相同的 html 文件(通常就是 index.html),因此对于这个 URL www.test.com/page1 ,哪怕服务器找不到,也给你返回 index.html,之后再使用前端路由来接管渲染相应的内容。

具体的配置方法取决于你的服务器软件。

例如你使用的 express.js,那么你可以添加如下的配置:

app.get('*', function (request, response){
  response.sendFile(path.resolve(__dirname, 'public', 'index.html'))
})

如果你使用的是 nginx,那么你需要添加如下的配置:

location / {
  try_files $uri /index.html;
}

location 对象

本节课最后,再来复习一下 location 对象,这个也是浏览器环境中的一个原生对象,通过这个对象可以获取到和 location 相关的众多信息。

例如:

除了上面的属性以外,该对象还支持一些方法:

  • assign 方法:载入一个新的文档
  • reload方法:重新载入当前文档
  • replace 方法:用新的文档替换当前文档

不过对于开发单页应用来讲,上面的方法一般不会用到,因为单页应用的特点就是一个页面,没有刷新的,也就是说至此至终都是一个文档,不会载入新的文档。

posted @ 2025-07-16 14:24  Zhentiw  阅读(8)  评论(0)    收藏  举报