Chrome | Safari | Firefox | Opera | IE | Android | iOS |
---|---|---|---|---|---|---|
31+ | 7.1+ | 34+ | 11.50+ | 10+ | 4.3+ | 7.1+ |
使用pushState()改变url而不刷新, ajax与HTML5 history pushState/replaceState实例, 详解history.pushState和history.replaceState以及page ajax的实现
我们使用location.hash来模拟ajax的前进后退功能。使用location.hash存在下面几个问题:
1.使用location.hash会导致地址栏的url发生变化,用户体验不够友好。
2.location.hash产生的历史记录无法修改,每次hash改变都会导致产生一个新的历史记录。
3.location.hash只是1个字符串,不能存储很多状态相关的信息。
为了解决这些问题,HTML5中引入了history.pushState()、history.replaceState()、popstate事件来处理浏览器历史记录的问题。下面的代码可以达到跟location.hash相同的效果,可以看到地址栏url不会改变。
效果如下:实例DEMO 1 | 实例DEMO 2 | 实例DEMO 3

HTML5 新增的历史记录 API 可以实现无刷新更改地址栏链接,配合 AJAX 可以做到无刷新跳转。
简单来说:假设当前页面为justcode.ikeepstudying.com
,那么执行下面的 JavaScript 语句:
window.history.pushState(null, null, "/profile/");
之后,地址栏的地址就会变成justcode.ikeepstudying.com/profile/
,但同时浏览器不会刷新页面,甚至不会检测目标页面是否存在。
state是什么?
状态对象(state object) — 一个JavaScript对象,与用pushState()方法创建的新历史记录条目关联。无论何时用户导航到新创建的状态,popstate事件都会被触发,并且事件对象的state属性都包含历史记录条目的状态对象的拷贝。(原文)
说白了,state就是我们用于保存到history序列中的一个数据集合。我们可以在pushState之前,先构建自己的state,里面可以存放很多我们在监听window.popstate事件时所需要的东西。
例如,我们用代码来看:
var state = { title: title, url: url, content: $('body').html(), prev: window.location.href, time: (new Date()).getTime() }; history.pushState(state, title, url);
上面title和url两个值是其他地方已经准备好的。通过上面这段代码,在history序列中就多出一个state,并且可以通过window.popstate识别。
而在window.popstate的监听动作中,我们可以这样去做:
window.onpopstate = function(event) { var state = history.state; // 等价于 var state = event.state; if(state && state.content) $('body').html(state.content); };
当点击浏览器的前进或后退按钮时,popstate事件被触发,浏览器会到history序列中去找到上一个或下一个state,并且将这个state放到event.state中。这个时候,该state就是当前浏览器所显示的页面所使用的state了,所以使用history.state可以读取到同样的内容。
而这个时候,该state中,正好保存着我们在pushState时,保存到content内容,直接使用它即可。为了实现title,css,js之类的进一步重载,你还可以做更多的内容保存和逻辑处理。
从上面可以看到,当我们在使用pushState和popstate时,可以在脑海中先想象一个history序列,通过pushState加入一个新state到序列中,通过history.state取出当前屏幕显示区域所需要的state。
replaceState和pushState的区别
虽然从上面的讲解中,我们大致掌握了state序列的问题,但是,我们还有一个点没有讲到,那就是改变url。毫无疑问,我们知道通过replaceState可以无刷新页面改变url。
history.replaceState(state,title,url);
其中,state,title,url都是我们事先准备好的。其中,要点就是url,如果我们仅仅希望实现URL的变化,甚至可以做下面这个实验:
history.replaceState(null,null,'/test.html');
把这段代码加入到页面后,观察url的变化。实际上,整个页面没有发生任何变动,唯独url改变了。
这个时候,我们回头来看replaceState之后,state的一些变化。通过下面这段代码来试验:
console.log(history.state); // 得到一个内容丰富的state内容 history.replaceState(null,null,'/test.html'); console.log(history.state); // 得到null
这说明replaceState将对当前显示界面对应的state发生作用,甚至替换state的内容,这是一个会产生负影响的操作,实际上,我们希望通过pushState将一个配置好的state加入到history序列中,但是replaceState改变了我们加入到序列中的state的内容。因此,我们必须保证replceState的时候,所传入的state参数所我们想要使用的state的内容。这个很难理解,我们用一张图来解释:
上图中,1是原本的history序列,我们在阅读到某个界面到时候,执行pushState操作,使history序列变成了2的状态,再对pushState的state执行replaceState后,变成了3的状态。从2变为3这个过程,把原本的state修改了,我们要想清楚,我们是要把{title,content}放入history还是把{name,number}放入history,如果是前者,那么就要在replaceState的时候,格外小心。
配合ajax实现无刷新切换url的页面跳转
众所周知,Ajax可以实现页面的无刷新操作——优点;但是,也会造成另外的问题,无法前进与后退!曾几何时,Gmail似乎借助iframe搞定,如今,HTML5让事情变得如同过家家般简单。
当执行Ajax操作的时候,往浏览器history
中塞入一个地址(使用pushState
)(这是无刷新的);于是,返回的时候,通过URL或其他传参,我们就可以还原到Ajax之前的模样。
本demo所展示的就是ajax的内容载入与地址栏的前进与后退,典型应用,对于熟悉相关知识点很有帮助。
您可以狠狠地点击这里:HTML5 history API与ajax分页实例
demo结构大致如下:左边导航菜单,右侧详细内容。

如果我们想偷懒,导航直接URL地址,点击刷新得了。但头尾内容都是一样的,刷新总显得浪费。从体验上讲,点击导航,右侧Ajax局部刷新是更优的策略。
Ajax局部刷新小菜,稍有经验都能轻松应对。现在如果提出如下需求:每次ajax刷新就如果页面刷新一样,可以后退查看之前内容,怎么破?
我的策略如下:
- 每次手动点击左侧的菜单,我将Ajax地址的查询内容(
?
后面的)附在demo HTML页面地址后面,使用history.pushState
塞到浏览器历史中。 - 浏览器的前进与后退,会触发
window.onpopstate
事件,通过绑定popstate
事件,就可以根据当前URL地址中的查询内容让对应的菜单执行Ajax载入,实现Ajax的前进与后退效果。 - 页面首次载入的时候,如果没有查询地址、或查询地址不匹配,则使用第一个菜单的Ajax地址的查询内容,并使用
history.replaceState
更改当前的浏览器历史,然后触发Ajax操作。
于是,你会看到:
- 页面首次载入,虽然我们访问的URL的后缀是光秃秃的
.html
,但是,实际URL最后是:使用pushState()改变url而不刷新, ajax与HTML5 history pushState/replaceState实例, 详解history.pushState和history.replaceState以及page ajax的实现 因为被
history.replaceState
摆了一道。 - 鼠标点击左边的任意一个菜单,会发现,右侧内容虽然是Ajax载入,但是,页面的URL地址却变了,例如,点击宝山区:
使用pushState()改变url而不刷新, ajax与HTML5 history pushState/replaceState实例, 详解history.pushState和history.replaceState以及page ajax的实现 因为历史记录被
history.pushState
插了一刀。 - 此时,我们点击地址栏的后退按钮,就是这个:
使用pushState()改变url而不刷新, ajax与HTML5 history pushState/replaceState实例, 详解history.pushState和history.replaceState以及page ajax的实现 奇迹般的,页面无刷新的,又回到了浦东菜单:
使用pushState()改变url而不刷新, ajax与HTML5 history pushState/replaceState实例, 详解history.pushState和history.replaceState以及page ajax的实现 因为
window.onpopstate
让他又出来。
HISTORY API
打开浏览器的控制台并输入window.history
或简单地输入history
。在现代浏览器中,您应该看到以下内容:

PUSHSTATE
将给定状态推送到会话历史记录,具有给定标题,以及可选的给定URL。
句法:
history.pushState(state, title, url);
参数:
- state 状态对象是表示用户界面状态的对象。当用户导航到新状态时,
popstate
将触发事件,并且state
事件的属性等于历史状态对象。 - title(目前被忽略)state的头衔。
- url (可选)状态的URL。绝对和相对URL都被接受。
history.pushState(null, null, 'new-page.html'); // or history.pushState({url: 'new-page.html'}, 'New Page'); // or history.pushState({url: 'new-page.html', page: 2}, 'New Page', 'new-page.html');
POPSTATE事件
每次当前历史记录条目更改时,都会触发popstate事件。当这种情况发生在用户点击浏览器的后退/前进按钮或编程调用history.back()
,hitory.forward()
,history.go()
方法。
// jQuery $(window).on('popstate', function (e) { var state = e.originalEvent.state; if (state !== null) { //load content with ajax } }); // Vanilla javascript window.addEventListener('popstate', function (e) { var state = e.state; if (state !== null) { //load content with ajax } });
工作实例
下一个示例演示了真正的单页应用程序应该是什么样子。查看我们的工作pushState演示。源代码可在此GitHub仓库中找到。
<script> $(function () { var load = function (url) { $.get(url).done(function (data) { $("#content").html(data); }) }; $(document).on('click', 'a', function (e) { e.preventDefault(); var $this = $(this), url = $this.attr("href"), title = $this.text(); history.pushState({ url: url, title: title }, title, url); document.title = title; load(url); }); $(window).on('popstate', function (e) { var state = e.originalEvent.state; if (state !== null) { document.title = state.title; load(state.url); } else { document.title = 'World Regions'; $("#content").empty(); } }); }); </script> <a href="africa">Africa</a> <a href="asia">Asia</a> <a href="europe">Europe</a> <a href="north-america">North America</a> <a href="oceania">Oceania</a> <a href="south-america">South America</a> <div id="content"></div>
单页应用SEO
您应该将请求与服务器区分开来,并将适当的内容发送到浏览器。例如:当通过XMLHttpRequest(AJAX请求)发出请求时,响应应仅包含所请求的内容(网页的特定部分)。对于经常要求你必须把整个网页,例如html
,head
, body
+页面内容。
为什么PUSHSTATE而不是HASH-BANGS
- 清除没有破坏RFC 3986的URL
- 甚至没有JavaScript的工作
- 不太关心抓取和索引
总之,由于现代浏览器被广泛使用,您应该考虑使用HTML5 pushState来构建您的单页应用程序,并且#!
如果您打算支持旧浏览器,请使用hash-bang方法。实际上,Facebook使用了一种双重方法 – 用于IE9的hash bang和用于现代浏览器的pushState。
history.pushState
history.pushState({}, "页面标题", "xxx.html");
history.replaceState
history.replaceState(null, "页面标题", "xxx.html");
window.onpopstate
window.addEventListener("popstate", function() { var currentState = history.state; /* * 该干嘛干嘛 */ });
浏览器兼容性
根据 MDN 提供的信息,IE 10, Chrome 5, Firefox 4, Safari 5 开始支持这个特性。Fallback 可以采用替换 hash 的方法。另外,History.js 库也提供了对老版本浏览器的 history API 支持(同样是利用替换 hash)。为了搜索引擎收录,可能需要使用#!
表示法。
效果如下:实例DEMO 1 | 实例DEMO 2 | 实例DEMO 3