学习HTML5 API (pushState/replaceState)

ajax大家都很熟悉了,我们知道ajax会有一些弊端,H5出来了两个新的API叫做pushState和replaceState,这两个可以很好的解决ajax不能历史返回的问题。先推荐一个博客,推荐的原因是要知其所以然,知道历史才能够更好的理解。下面是我的整理,由于是自己的整理,所以很多无关这两个API的,可以直接忽略,跳过直接看后面重点部分和demo。。


前言-ajax与传统web应用的比较

简单来说:
第一:是局部刷新,传统web应用要请求并重新读取一个整个页面。
第二:仅仅向服务器发送并取回必要的数据,不会像传统的web应用一样浪费很多带宽。
第三:减少了数据交换,减少了响应时间,响应的更快。

用户体验好,用户体验好,用户体验好

传统的Web应用交互由用户触发一个HTTP请求到服务器,服务器对其进行处理后再返回一个新的HTHL页到客户端, 每当服务器处理客户端提交的请求时,客户都只能空闲等待,并且哪怕只是一次很小的交互、只需从服务器端得到很简单的一个数据,都要返回一个完整的HTML页,而用户每次都要浪费时间和带宽去重新读取整个页面。这个做法浪费了许多带宽,由于每次应用的交互都需要向服务器发送请求,应用的响应时间就依赖于服务器的响应时间。这导致了用户界面的响应比本地应用慢得多。
与此不同,AJAX应用可以仅向服务器发送并取回必需的数据,它使用SOAP或其它一些基于XML的Web Service接口,并在客户端采用JavaScript处理来自服务器的响应。因为在服务器和浏览器之间交换的数据大量减少,结果我们就能看到响应更快的应用。同时很多的处理工作可以在发出请求的客户端机器上完成,所以Web服务器的处理时间也减少了。


ajax的工作原理

Ajax的工作原理相当于在用户和服务器之间加了—个中间层(AJAX引擎),使用户操作与服务器响应异步化。并不是所有的用户请求都提交给服务器,像—些数据验证和数据处理等都交给Ajax引擎自己来做, 只有确定需要从服务器读取新数据时再由Ajax引擎代为向服务器提交请求。

其实Ajax主要就是通过XMLHttpRequest对象来向服务器发送异步请求,然后从服务器端获得相应的数据,利用相应的数据来局部更新相应的页面。


ajax的缺点和优点

咱们先讲优点:

  1. 无刷新更新数据避免修改了那些不需要改变的信息,从而减少等待时间,用户体验很好。
  2. 异步通信中途不会阻止用户操作,具有更加迅速的响应能力。优化了b/s的沟通。
  3. 前后端负载均衡,后面的ajax的工作原理会讲到,ajax可以吧以前必须要服务器承担的工作留给客户端。利用客户端闲置的能力来处理,减轻服务器和带宽的负担,节约空间和宽带租用成本。并且减轻服务器的负担,ajax的原则是“按需取数据”,可以最大程度的减少冗余请求,和响应对服务器造成的负担。
  4. 基于标准,被支持
  5. 数据与呈现分离有利于分工合作。减少非技术人员对web应用程序错误。

再来看看缺点:

  1. ajax破坏了浏览器本身的机制,不能够有back和history的功能。
  2. ajax的安全问题,这使得开发者在不经意间会暴露更多的数据和服务器逻辑。ajax也难以避免一些一直的安全弱点,sql注入,xss等等。
  3. 破坏程序的异常处理机制(这一点在怎么理解?)
  4. 违背URL和资源定位的初衷,例如,我给你一个URL地址,如果采用了Ajax技术,也许你在该URL地址下面看到的和我在这个URL地址下看到的内容是不同的。这个和资源定位的初衷是相背离的。
  5. 对搜索引擎支持较弱。对搜索引擎的支持比较弱。如果使用不当,AJAX会增大网络数据的流量,从而降低整个系统的性能。
  6. 还有不容易调试那些问题。

重点:怎么解决不能有back和history这一问题?

先来看看history对象:

1
2
3
4
5
6
window.history.back();
window.history.forward();
window.history.go(-1);
window.history.go(2);
//通过检查浏览器历史记录的length属性来找到历史记录堆栈中的页面总数
var num = window.history.length;

H5引入了history.pushState(),history.replaceState()这两个API,他们允许添加和修改history实体。需要和window.onpopstate事件一起工作。这两个方法用来修改referrer,可以被用在修改状态后而为XMLHttpRequest对象创建的http header中。

pushState就是向history添加当前页面的记录,replaceState的参数和用法和pushState相同,区别在于replaceState是用于修改当前页面在history中的记录。这个我们后面也会在例子中用到。

其实pushState就是一个往堆栈添加一条历史记录。可以形象的理解成push。

pushState(data,title,[,url]):data为一个对象或者为null,它会在触发window的popstate事件时,作为参数的state属性传递过去,title为页面的标题,现在的浏览器忽略掉了这个参数,所以写啥都没用,url为页面的URL,如果不写的话,就是当前页了。

replaceState(data,title,[,url]):参数时相同的,这种更改不会访问该URL。也就是不会刷新。会让用户看到的url发生变化,如果我们需要访问该URL,还要去手动的触发。

replaceState不会在window.history里新增历史记录点,其效果类似于window.location.replace(url),都是不会在历史记录点里新增一个记录点的。当你为了响应用户的某些操作,而要更新当前历史记录条目的状态对象或URL时,使用replaceState()方法会特别合适。


PJAX

刚刚我们已经说到了那两个API,我们的PJAX就是基于上面新特性来的。PJAX是别人开发的一个项目,地址是:https://github.com/defunkt/jquery-pjax。

它不仅仅支持了局部刷新这一特点,而且在刷新页面的同时,浏览器地址栏上面的地址是会更改的,用浏览器回退功能也能够回退回去,正常我们用ajax是实现不了的。

PJAX的基本思路是,当用户点击一个链接,通过ajax更新页面变化的部分,然后使用HTML5的pushState修改浏览器的URL地址,这样有效地避免了整个页面的重新加载。如果浏览器不支持history的两个新API或者JS被禁用了,那这个链接就只能跳转并重新刷新整个页面了。和传统的ajax设计稍微不同,ajax通常是从后台获取JSON数据,然后由前端解析渲染,而PJAX请求的是一个在服务器上生成好的HTML碎片

看看如果我们自己写是怎么样利用这两个API的。


先来看一个完整的例子

demo的地址我放在了http://cailidan.cn/web/demo/testajax这里。
一个很简单的php文件,通过接收不同的id,并且返回不同的数据。显示到textarea上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php 	
$id = isset($_POST['id'])?$_POST['id']:1;
switch($id){
case 1:
echo "这是一个ajax请求---page1";
break;
case 2:
echo "这是一个ajax请求---page2";
break;
case 3:
echo "这是一个ajax请求---page3";
break;
default:
echo "default ajax";
}
?>

下面的例子是这样一个过程:

  1. 当我们访问index.html的时候,会自动显示成http://localhost:801/testajax/index.html?page=1,原因就是我们使用了用history.replaceState更改当前的浏览器历史;并且在textarea部分显示这是一个ajax请求---page1
  2. 当我们点击每一个li的时候,对应的textarea会发生变化,这没有什么新奇的,但当我们看浏览器的URL,发现对应的url也会发生变化,如http://localhost:801/testajax/index.html?page=2这个是我们利用replaceState来做到的。
  3. 然后我们试一下点击回退,发现是可以回到上一级哒。是不是很棒呢?
  4. 我们的思路是,先给每个li绑定一个事件,然后当我们点击这个li的时候,调用ajax,并把当前的url拼凑后pushState。当我们首次访问的时候query=undefined,我们获取到undefined后,就默认是第一个,此时我们利用replaceState改变状态栏的信息。若状态栏的query有值。,则判断query对应的li是哪个。如果找到了该li,触发其click事件,如果没有的话,证明query传参错误。然后默认去触发第一个。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试pjax</title>
</head>
<body>
<ul id="list">
<li><a href="test.php?page=1" data-id="1">page1</a></li>
<li><a href="test.php?page=2" data-id="2">page2</a></li>
<li><a href="test.php?page=3" data-id="3">page3</a></li>
<li><a href="test.php?page=4" data-id="4">page4</a></li>
</ul>
<textarea name="" id="textarea" cols="30" rows="10">

</textarea>
<script src="./jquery-1.12.0.min.js"></script>
<script>
$(document).ready(function(){
var list = $("#list a").bind("click",function(e){
var id = $(e.target).attr('href').split('?')[1].split("=")[1];
var query = this.href.split("?")[1];

//调用ajax,返回相应的值入textarea
if(history.pushState && query){
$.ajax({
type:"POST",
data:{id:id},
url:"test.php",
success:function(data){
$("#textarea").html(data);
}
})

var title = "测试page"+$(this).text();
document.title = title;
if(e){
history.pushState({title:title},title,location.href.split('?')[0]+"?"+query);
}
}
return false;
});

var trigger = function(target){
var query = location.href.split("?")[1];
var target = target || null;

//开始时query为空,就让target为第一个li选项
if(typeof query == "undefined"){
target = list[0];
//把url状态栏中的内容显示为如http://localhost:801/testajax/index.html?page=1
history.replaceState(null, document.title, location.href.split("#")[0] + "?" + target.href.split("?")[1]) + location.hash;
trigger(target);
}else{
//如果已经有query了,获取里面的query值找到对应的li,并设置target为相应的li
list.each(function(){
if(target === null && this.href.split("?")[1] === query){
target = this;
}
});
//如果上面target没有赋值,也就是说query找不到对应的li,证明query不存在相应的记录
//此时就设置为如http://localhost:801/testajax/index.html
//如果target有值,则触发click事件,调用ajax.
if(!target){
history.replaceState(null,document.title,location.href.split('?')[0]);
trigger();
}else{
$(target).trigger("click");
}
}
}

if(history.pushState){
trigger();
//每次浏览器前进或者后退的时候触发
$(window).bind("popstate",function(){
trigger();
});
}
})
</script>
</body>
</html>

顺便记录几个XMLHttpRequest的属性

onreadystatechange 每次状态改变所触发事件的事件处理程序。

responseText 从服务器进程返回数据的字符串形式。

responseXML 从服务器进程返回的DOM兼容的文档数据对象。

status 从服务器返回的数字代码,比如常见的404(未找到)和200(已就绪)

status Text 伴随状态码的字符串信息

readyState 对象状态值

0 (未初始化) 对象已建立,但是尚未初始化(尚未调用open方法)

1 (初始化) 对象已建立,尚未调用send方法

2 (发送数据) send方法已调用,但是当前的状态及http头未知

3 (数据传送中) 已接收部分数据,因为响应及http头不全,这时通过responseBody和responseText获取部分数据会出现错误,

4 (完成) 数据接收完毕,此时可以通过通过responseXml和responseText获取完整的回应数据。

总结

感谢osChina张鑫旭的这篇文章。了解了H5新的API,也大致的知道是怎么用的了。