[Javascript] History API
history 是浏览器环境中所支持的一个对象,该对象用于管理当前创建最近访问过的 URL 历史记录,所有的 URL 会被存储在一个名为 histroy 的对象里面,回头就可以通过 JS 脚本调用 history 对象的方法从而控制浏览器前进或者后退。
例如打开浏览器,新创建一个标签页会话,然后在控制台输入 history,那么就能够看到这个对象

-
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 相关的众多信息。
例如:
-
hash:如果 URL 中包含有 #,该方法将返回该符号之后的内容。
例如:http://www.example.com/index.html#welcome 的 hash 是 #welcome。 -
host:服务器的名字,例如 www.example.com 。
-
hostname:通常等于 host,有时会省略前面的 www。
-
href:当前页面载入的完整 URL。
-
pathname:URL 中主机名之后的部分。
例如:http://www.example.com/html/js/jsbasic/2010/0319/88.html 的 pathname 是 /html/js/jsbasic/2010/0319/88.html。 -
port:URL 中声明的请求端口。默认情况下,大多数 URL 没有端口信息(默认为 80 端口),所以该属性通常是空白的。
例如 http://www.example.com:8080/index.html 这样的 URL 的 port 属性为 '8080'。 -
protocol:URL 中使用的协议,即双斜杠 // 之前的部分。
例如 http://www.example.com 中的 protocol 属性等于 'http:',ftp://www.example.com 的 protocol 属性等于'ftp:'。 -
search:执行 GET 请求的 URL 中的问号 ? 后的部分,又称查询字符串。
例如 http://www.example.com/search.html?tern=sunchis 中 search 属性为 ?term=sunchis。
除了上面的属性以外,该对象还支持一些方法:
- assign 方法:载入一个新的文档
- reload方法:重新载入当前文档
- replace 方法:用新的文档替换当前文档
不过对于开发单页应用来讲,上面的方法一般不会用到,因为单页应用的特点就是一个页面,没有刷新的,也就是说至此至终都是一个文档,不会载入新的文档。