html5新特性:利用history的pushState等方法来解决使用ajax导致页面后退和前进的问题

时间:2022-11-05 12:48:56

一、背景

使用ajax,可以实现不需要刷新整个页面就可以进行局部页面的更新。这样可以开发交互性很强的富客户端程序,减少网络传输的内容。但长期以来存在一个问题,就是无法利用浏览器本身提供的前进和后退按钮进行操作。比如在页面执行某个动作,该动作利用ajax请求到服务器获取数据,更新了当前页面的某些内容,这时想回到操作前的界面,用户就会习惯点击浏览器的后退按钮,实际这里是无效的(要么页面没反应,要么打开一个前面打开的过的页面),或者想收藏当前页面(以便于重新打开时直接显示当前的信息),也是无法做到的。

这个问题因为html5的新特性而得以可以解决。但不是直接解决了。而是提供了一些新的api,需要程序员编写代码来实现。下面我们将详细的来介绍。

如果你对此问题和html5的这些新特性已经有些了解,可以直接跳到最后的案例章节。

二、history对象分析

浏览器是通过 window对象的 history对象来对浏览器历史访问记录,从而可以实现前进和后退。history对象可以理解其保存了一个有序的列表对象,每个对象都代表了一个页面信息(包括页面的url等信息),注意当前页面也被保存在里面。

这样就可以通过浏览器本身提供的前进和后退按钮来操作,也可以利用javascript调用history对象的back(),forward(),和go()方法来实现页面的切换。

我们先来理解下history的机制。history对象中记录了浏览器窗口访问过的url,但出于安全考虑,无法通过程序获取history对象中的具体信息,只能通过back、forward、go方法进行页面跳转,此外length属性记录了history中的记录(url)条数。

我们设想下,当在浏览器窗口打开第一个地址,比如 url1时, 这时history中就有了url1这个记录,且length属性值为1,history对象中有个当前页面指针(从概念上可以这么理解)指向url1;如果再打开一个url2页面(无论是通过在地址栏直接输入、或通过url1中的链接或js代码打开),这时history中就有了url1和url2这两个记录,是一个有序的列表,这时length属性值为2,history对象中的当前页面指针指向url2,这时url2是最新的页面,页面不可以前进,但可以后退到url1,这时如果点击浏览器本身提供的后退按钮(或用js调用back方法),这时url1页面会被重新加载显示,history对象的length仍然为2,url1和url2组成的列表仍然不变,但history对象中的当前页面指针指向url1了,这时就不能后退但可以前进了。可以理解成一个数据结构中的双向链表机制。

通过上面的描述我们可以看出,我们说的历史记录都是指一个完整的页面请求url,而ajax并不是一个完整的页面请求,因此浏览器无法记录ajax的操作信息。

三、history对象的新特性

HTML5引入了histtory.pushState()和history.replaceState()这两个方法,它们会更新history对象的内容。同时,结合window.onpostate事件,就可以解决文章开头提出的问题。

 我们先来看pushState方法的含义,我们通过举例子的方式来更好的说明,先给出一段代码:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试</title>
<script type="text/javascript" src="jquery.min.js"></script>
</head> <body>
<button onclick="doPushState()">pushState</button>
<button onclick="count()">count</button> <script>
var index=1;
$(function(){
alert(location.href);
}); function doPushState(){
history.pushState({}, "newtitle","test"+(index++)+".html");
} function count(){
alert(window.history.length);
}
</script>
</body> </html>

上面是一个完整的html文件,文件名为demo.html。 把该文件放到web服务器上,从浏览器访问。如果是直接从本地磁盘打开,文件中的js代码执行会报错误。
在一个新的浏览器窗口访问该demo.html。 首先会执行 $()方法,弹出代码中的location.href信息。 这时执行count按钮,显示为1,注意如果在ie或chrome的新的浏览器窗口打开,值可能为2,因为它们的窗口会加载系统默认的一个页面,不是一个空白的窗口。

这时我们每点击一下pushState按钮,发现浏览器的地址会发生变化,先后变为test1.html , test2.html, test3.html, .......,并且通过点击count按钮发现,弹出的值加1. 这说明每调用一次pushState方法,history中就会新增加一条url记录。

我们先来解释下pushState方法,该方法有三个参数:

1)第一个参数是个js对象,可以放任何的内容,可以在onpostate事件中(后面介绍)获取到便于做相应的处理。

2)第二个参数是个字符串,目前好像没有起作用,可以传个空串。

3)第三个参数是个字符串,就是保存到history中的url。

结合例子的代码和输出可以看出,调用pushState方法的作用,就是相当于打开一个新页面,把当前页面作为历史记录,而当前的地址栏显示的是pushState方法中的url(这里是test.html)。但是与普通的打开一个新页面不同。浏览器将不会在调用pushState()方法后加载这个url,也就是说即使你写一个错误的url,也不会报错。

可以这么理解,当我们在一个新的浏览器窗口打开 demo.html后,点击n次pushState按钮后,history对象中存在这样的一个ulr列表。

demo.html(第1个url)----> index.html(第2个url).......->index?.html(第n个url)----->indexn.html(当前页面的url)。

这时我们需要点击浏览器上的回退按钮n次,才能将浏览器上的地址退回到 demo.html。而且无论是在点击pushState按钮 或点击回退按钮的过程中发现,$()方法根本没有被触发,也就是说整个过程浏览器的页面内容都没有发生变化,变化的只是地址信息。

这也进一步说明,pushState只是将当前页面保存到history的历史记录中(并作为最近的一个记录),并且将当前浏览器的地址栏改为参数url的指定的值,但并不会加载它。这点与普通的通过链接打开或浏览器地址输入url完全不一样。

到了这里我们可以想象一下文章开头提出的问题了,如果我们在页面中执行一个ajax操作,当操作成功(如更新页面的局部内容)后,我们通过代码调用pushState方法,设置一个新的url,这样看上去就像发起了一个全新的请求,实际上只是个ajax操作。这时回退按钮也能用了,问题仅仅这样,回退按钮点了也没有任何反应。如果我们能通过代码,来响应这个回退按钮触发的事件,在事件中让界面恢复到ajax请求之前的界面,问题不就解决了吗?

得确如此,解决思路就是上面说的。下面我们来通过一个实际的例子看如何实现。在介绍例子之前,我们先来解释下html5中 history新增的另一个方法replaceState方法。

replaceState方法与pushState类似,同样有三个参数。区别在于,replaceState()是用来修改history对象中记录的当前页面的信息,它不是新建一个记录。如果将上面例子中的 代码 history.pushState({}, "newtitle","test"+(index++)+".html"); 中的pushState改为replaceState,其它代码都不动。这时我们点击pushState按钮后,看到的现象是一样的,地址栏的地址不断变化,页面内容不变。但我们点击count按钮,发现history中的记录数不变。这说明replaceState只是改变当前页面在history对象中的记录信息;而pushState是会产生一个新记录作为当前记录,把当前页面作为历史的记录保存。

我们再来看下window对象的popstate事件,当进行页面的前进或回退时,会触发该事件,并且在事件响应函数中通过 history.state 可以获取到 pushState方法和replaceState方法中第一个参数指定的对象。

解释了这几个api后,我们来一个具体的例子。

四、具体案例

我们来设想这样一个应用。一个页面来显示一篇长文章,该文章内容很长,分为很多章节。我们希望页面不会一次把所有章节的内容都加载起来,而是有一个章节导航,点击每个章节链接,通过jax加载具体章节的内容,而其它页面部分不需要要变化。

我们先看下传统的实现代码(注意,这里只注意核心逻辑代码的实现,其它的页面布局等尽量简化):

<!DOCTYPE html>
<html lang="en"> <head>
<meta charset="UTF-8">
<title>test2</title>
<style type="text/css">
div {
padding-bottom:100px;
}
</style>
<script type="text/javascript" src="jquery.min.js"></script>
</head> <body>
<div style="float:left;border:1px solid red;margin:20px">
<p><a href="javascript:;" id="section1">第1章</a></p>
<p><a href="javascript:;" id="section2">第2章</a></p>
<p><a href="javascript:;" id="section3">第3章</a></p>
</div>
<div style="float:left;border:1px solid red;margin:20px" id="content"> </div>
<script>
$(function(){
//添加链接的处理事件
$("a").click(ajax);
//加载默认的章节,默认显示第1章
$("#section1").trigger("click");
}); function ajax(event){
//实际的流程是发起ajax请求,获取内容并显示。这里为了简化,没有写实际的ajax请求。
//这段代码应该在ajax的请求响应中编写。
$("#content").html(this.id+"的内容");
var title = this.id;
document.title = title;
}
</script> </body> </html>

在浏览器加载该页面,当我们点击不同的章节链接时,内容会跟着变化,浏览器的标题也跟着变化。但是:

1)回退、前进按钮用不了

2)当我们刷新页面时,不管当前在哪个章节,都会重新回到第一个章节。

3)地址栏的url没有变化,也意味着我们没法把某个章节的地址保存下来,以后再次打开直接显示该章节内容。

上面就是传统ajax应用的一些弊端。下面我们就来解决这些问题。

我们先给出解决代码:

<!DOCTYPE html>
<html lang="en"> <head>
<meta charset="UTF-8">
<title>test2</title>
<style type="text/css">
div {
padding-bottom:100px;
}
</style>
<script type="text/javascript" src="jquery.min.js"></script>
</head> <body>
<div style="float:left;border:1px solid red;margin:20px">
<p><a href="javascript:;" id="section1">第1章</a></p>
<p><a href="javascript:;" id="section2">第2章</a></p>
<p><a href="javascript:;" id="section3">第3章</a></p>
</div>
<div style="float:left;border:1px solid red;margin:20px" id="content"> </div>
<script>
$(function(){
//添加链接的处理事件
$("a").click(ajax);
//加载默认的章节
changeContent();
//添加popstate事件
$(window).on("popstate",function(){
changeContent();
});
}); function changeContent(){
var query = location.href.split("?")[1];
if (!query) {
// 如果没有查询条件,则显示默认第1个章节
history.replaceState(null, "",
location.href + "?name=" + $("#section1")[0].id);
changeContent();
} else {
//触发按钮click事件,加载内容,
//注意不要漏了true参数,这样可以和用户直接点击触发的页面变化区别开来
$("#"+query.split("=")[1]).trigger("click",true);
}
} function ajax(event,isPopstate){
$("#content").html(this.id+"的内容");
var title = this.id;
document.title = title;
if(!isPopstate){
history.pushState(null, "", location.href.split("?")[0] + "?name=" + title);
}
} </script> </body> </html>

加载上面页面,测试下,所有的问题都解决了。下面我们来解释下上述代码。

  我们先看changeContent方法,该方法首先获取页面的url地址,判断该地址是否有查询条件(是否带章节信息),如果没有,认为要显示第一章节。我们利用history的replaceState方法来改变当前的url,加上 name=section1的查询条件,表示是第1章。因为replaceState方法不会改变页面内容,因此还需要接着再调用changeContent方法。如果地址带了查询条件,认为已经指定显示某个章节内容,这时触发章节链接的click事件。

  我们再看ajax方法,就是章节链接的click事件响应函数,为了简化,该函数没有发起实际的ajax请求,而是相当于直接处理ajax返回的结果。首先是用得到的结果更新页面(这里是直接写死的),然后更新标题,这与传统的ajax做法一样。关键的区别是,判断该方法如果是用户点击的(不是onpopstate事件处理的),就会调用history对象的pushState方法来将当前页面信息保存到history对象中,并新增一个记录信息代表ajax请求后的页面。

changeContent方法同样是onpopstate事件的处理函数,其功能就是利用获取到的url信息(保存在history记录)中,来通过ajax获取到对应的内容,让页面显示相应的信息。 从用户感知上看,就跟正常的回退、前进导致的页面切换一样。用户感觉不到是ajax请求,还以为就是多个独立的页面在切换。

五、小结

本文详细的介绍了如何利用html5的新特性来解决传统ajax请求导致的一些缺陷。通过上面的介绍可以看出,为了解决问题,还是需要程序员做不少的事情,对于一个实际的项目来说,最好能在框架层面进行封装解决,而不是要让每个具体页面的实现者都来处理。这个可以是下一步要考虑的内容。

html5新特性:利用history的pushState等方法来解决使用ajax导致页面后退和前进的问题的更多相关文章

  1. HTML5新特性之History

    几年前,Ajax的兴起给互联网带来了新的生机,同时也使用户体验有了质的飞跃,用户无需刷新页面即可获取新的数据,而页面也以一种更具有交互性的形式为用户展现视图,可以说这种变化对互联网发展的贡献是史无前例 ...

  2. HTML5新特性之CSS&plus;HTML5实例

    1.新的DOCTYPE和字符集 HTML5的一项准则就是化繁为简,Web页面的DOCTYPE被极大的简化. <!DOCTYPE html> 同时字符集声明也被简化了: <meta c ...

  3. html5新特性与用法大全了解一下

    有好多小伙伴私聊我问我html5新特性 和用法,下面我给大家具体介绍一下html5都新加了哪些新特性,下面我给大家总结一下. 1)新的语义标签 footer header 等等2)增强型表单 表单2. ...

  4. html5新特性contenteditable 属性更容易实现动态表单

    介绍html5新特性的一个属性:contenteditable 作用域全局.所有的块标签都可以,例如:span.p.div.td等标签.但是,不可以作用域<br/>类型的标签. conte ...

  5. html5新特性

    这一篇博文不会告诉你怎么去使用html5的新特性,只会给你总结一下新特性------对于好学的人可以把这篇文章当做一个目录 对于初接触的人来说是一个导向 对于已经接触过的人来说是一个检测你掌握程度的检 ...

  6. 转&colon; HTML5新特性之Mutation Observer

    转: HTML5新特性之Mutation Observer Mutation Observer是什么 Mutation Observer(变动观察器)是监视DOM变动的接口.当DOM对象树发生任何变动 ...

  7. HTML5新特性:FileReader 和 FormData

    连接在这里: HTML5新特性:FileReader 和 FormData

  8. web全栈架构师&lbrack;笔记&rsqb; — 03 html5新特性

    HTML5新特性 一.geolocation PC端 精度比较低 通过IP库定位 移动端 通过GPS window.navigator.geolocation 单次 getCurrentPositio ...

  9. HTML5新特性:范围样式

    原文出处:http://blog.csdn.net/hfahe/article/details/7381141        Chromium 最近实现了一个HTML5的新特性:范围样式,又叫做&lt ...

随机推荐

  1. Windows Maven package时报错问题的解决

    google了一把,看到别人的说法是Stack size 不够大. Stack Space用来做方法的递归调用时压入Stack Frame.所以当递归调用太深的时候,就有可能耗尽Stack Space ...

  2. LCIS 最长公共上升子序列

    这个博客好久没写了,这几天为了准备清华交叉研究院的夏令营,在复习大一大二ACM训练时的一些基础算法,正好碰到LICS,发现没有写在博客里,那就顺便记录一下好了. 参考链接:http://blog.cs ...

  3. Qt之启动外部程序

    简述 QProcess可以用来启动外部程序,并与它们交互. 要启动一个进程,通过调用start()来进行,参数包含程序的名称和命令行参数,参数作为一个QStringList的单个字符串. 另外,也可以 ...

  4. LaTeX Pdf to Word

    用LaTeX写的文稿,生成的pdf,如果要改成word文档,如何是最合适的方式? 查了很多帖子,比较靠谱的一种方式是先将pdf转成rtf格式,再用word打开rtf文件.也有直接从tex文件直接转成d ...

  5. springmvc学习笔记&lpar;理论&rpar;

    1.springmvc是什么? Spring Web MVC是一种基于Java的实现了Web MVC设计模式的请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将web层 进行职责解耦,基 ...

  6. fzu2150(bfs)

    题目链接:http://acm.fzu.edu.cn/problem.php?pid=2150 题意:在任意两处点火,求最短时间烧光所有草堆. 分析:由于n,m比较小,将所有草堆坐标记录下来,然后暴力 ...

  7. Word2016&OpenCurlyDoubleQuote;此功能看似已中断 并需要修复”问题解决办法

    Word2016"此功能看似已中断 并需要修复"问题解决办法 修复步骤: 1. 按Windows 键+R键,输入"regedit"打开注册表. 2.找到以下键值 ...

  8. Vivado2016旧工程IP移动到新工程

    前言 在VIVADO中图形化界面生成IP,在旧工程中使用后,如果想在新的工程中使用这个IP但又不想再次生成一次,则就需要把就旧工程中的IP导入到新工程中. 流程 1.旧工程IP生成的目录如下,IP为C ...

  9. tunnel sw

    tunnel sw openssh vpn httprltunnel BarbaTunnel ngrok Chisel https://github.com/jpillora/chisel/blob/ ...

  10. 七牛 OCR 接口调试 & 七牛鉴权 Token 应用

    接口规约 PHP 实现 核心代码 执行结果 源代码 Java 实现 核心代码 执行结果 源代码 [现学现卖]图片内容审核接口 Python 实现 核心代码 执行结果 源代码 Go 实现 核心代码 执行 ...