目录
[TOC]
# 1 简介
Ajax是一系列web技术开发的合集,这系列技术包括以下:
* 展现层的HTML(或者XHTML), CSS ,
* 动态显示和数据交互的DOM对象
* 数据的载体形式, JSON或者XML(基本不用了)
* 实现异步通信的XMLHttpRequest 对象,这是Ajax的核心
* 把这些技术整合起来的JavaScript
利用Ajax。web应用可以通过XMLHttpRequest实例发送数据, JavaScript 解耦数据接口获取数据更新展现层,而不用重新加载整个页面。
# 2 XMLHttpRequest 发送请求的步骤
XMLHttpRequest是Ajax的实现的核心。看下使用他的步骤
1. 实例化XMLHttpRequest对象获得一个实例。
2. 通过实例open(可以说初始化)一个请求, 设置发送类型和接口以及同步
3. 如有需要配置请求头,各种事件(error,timeout等)
4. 调用实例的send方法,发送http/https的请求
5. 服务器回调,客户端接收,并做响应处理
# 3 实例代码
先看实例代码
## 3.1get 方法
<pre>//步骤一:获得实例
var xhr = new XMLHttpRequest();
/步骤二:.初始化请求
xhr.open("POST",“server.php”)
//步骤三: 设置报头,各种事件
xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded"); //这是必不可少的,告诉服务器传输的类型
xhr.timeout = 2000; // 超时时间,单位是毫秒,必须设置,否则ontimeout无效
xhr.onload = function () {
// 请求完成。在此进行处理。
};
xhr.ontimeout = function (e) {
// XMLHttpRequest 超时。在此做某事。
};
//步骤四:发送请求
var param = {参数,一般为JSON格式}
xhr.send(param)
//步骤五:.readyState改变时,监听请求是否完成,完成时处理服务器返回的数据
xhr.onreadystatechange = function() {
//请求完成
if(xhr.readyState === 4 ) {
//请求成功,处理数据
if(xhr.status === 200) {
//解析数据
var date = JSON.parse(xhr.responseText)
//do something....
}else{
//请求失败,返回http状态码。
alert(xhr.status)
}
}
}
</pre>
## 3.2 步骤二解析
步骤二调用`open()`进行初始化,参数为
<pre>open(
请求方法:一般为get,post。当然也有put,delete,
url: 请求后台地址,
async: 是否执行异步操作,默认为true,不要作死随便改成false。
)</pre>
如果请求方法为`get`并需要发送参数。参数在url后由`?`开头,多个参数由`&`拼接
例如
xhr.open("GET",“server.php?参数名=参数值&参数名二=参数值二");
参数名由前后端约定
POST传参在步骤四`send()`进行
## 3.3 步骤三解析
这个步骤可以做各种事件, 比如
* `XMLHttpRequestEventTarget.onabort`
当请求失败时调用该方法
* `XMLHttpRequestEventTarget.onerror`
当请求发生错误时调用该方法,输出
* `XMLHttpRequestEventTarget.onload`
当一个HTTP请求正确加载出内容后返回时调用。
* `XMLHttpRequestEventTarget.onloadstart`
当一个HTTP请求开始加载数据时调用。可以用来显示loading图片
* `XMLHttpRequestEventTarget.onprogress`
间歇调用该方法用来获取请求过程中的信息。
* `XMLHttpRequestEventTarget.ontimeout`
当时间超时时调用;只有通过设置XMLHttpRequest对象的timeout属性来发生超时时,这种情况才会发生。
* `XMLHttpRequestEventTarget.onloadend`
当内容加载完成,不管失败与否,都会调用该方法,可以用来隐藏loading图片
注意: 所有相关的事件绑定必须在调用send()方法之前进行.
步骤三除了做各种事件, 还有一个比较事情,就是设置请求头。
### 3.3.1 setRequestHeader
参数为 `setRequestHeader(
DOMString header,
DOMString value
);`
header需要设置为`"Content-Type"`,字符串格式。
`Content-Type`是非常重要的内容,在请求中,需要通过`Content-Type`准确告诉服务器请求时携带数据的MEMI类型。否则服务器不能真确处理携带数据。在响应中,服务器也需要在后端文件头部设置`Content-Type`,告诉客户端响应的数据类型。
格式一般为`Content-Type: [type]/[subtype]; parameter`
*type*有下面的形式
* <b>Text</b>:用于标准化地表示的文本信息,文本消息可以是多种字符集和或者多种格式的;
* <b>Multipart</b>:顾名思义,携带的数据由不同类型数据组成,比如头像上传时请求中有图片和用户id的文本信息;
* <b>Application</b>:用于传输应用程序数据或者二进制数据;
* <b>Message</b>:用于包装一个E-mail消息;
* <b>Image</b>:用于传输静态图片数据;
* <b>Audio</b>:用于传输音频或者音声数据;
* <b>Video</b>:用于传输动态影像数据,可以是与音频编辑在一起的视频数据格式。
*subtype* 作为一个补充信息,
标准的文本编码格式`application/x-www-form-urlencoded`
比如后端返回的是JSON格式信息,就是 `Content-Type:application/json`
JavaScript文件则是`Content-Type:application/x-javascript`
图片,混合文本信息的: `Content-Type:multipart/form-data`
如果是单独图片,则是`Content-Type:Image/jpg`
*parameter*更多时候是用来指定text/plain和text/htm 等文字的 编码形式 比如`Content-Type: application/json;charset=utf-8`
下面是一个在控制台netWorkg工具截下的headers。

form Data是POST请求携带的数据,编码为标准格式。而服务器响应的则是json格式。另外get请求一般不需要额外设置Content-Type,浏览器会默认用 `text/plain`
## 3.4步骤四解析
post请求携带的数据通过send()发送。
## 3.5步骤五解析
### 3.5.1 readyState
readyState返回的是请求的状态,一共五种状态
| 值 |状态 |描述 |
| --- | --- | --- |
| 0 | UNSENT (未打开) | open()方法还未被调用. |
| 1 | OPENED (未发送) | send()方法还未被调用. |
| 2 | HEADERS_RECEIVED (已获取响应头) | send()方法已经被调用, 响应头和响应状态已经返回. |
| 3 | LOADING (正在下载响应体) | 响应体下载中,responseText已经获取了部分数据 |
| 4 | DONE (请求完成 | 整个请求过程已经完毕 |
### 3.5.2 onreadystatechange
每当readyState的值改变时,onreadystatechange事件会被触发。当readyState的值为4时,说明请求完成,响应体也下载完毕。就能进行响应操作了。
### 3.5.3 status
status 输出服务器返回的HTTP状态码。常见的200请求成功, 304响应体文件已经缓存本地,不需要再下载响应体。在404请求地址不存在。5xx是服务器出现问题
更多参见[http状态码详解](http://tool.oschina.net/commons?type=5)
### 3.5.4 responseText
可以读取responseText 获得后端返回的响应体, 响应体是JSON字符串格式,需要用JSON.parse()方法解析才能使用。
# 4 跨域
## 4.1什么是跨域
使用Ajax,很多人会遇到跨域的问题,跨域是的根本原因是浏览器`同源策略`引起的。所谓同源是指三个相同
* 协议相同
* 域名相同
* 端口相同
举例来说,`http://www.example.com/dir/page.html`这个网址,协议是http://,域名是`www.example.com`,端口是80(默认端口可以省略)
* `http://www.example.com/dir2/other.html`:同源
* `http://example.com/dir/other.html`:不同源(域名不同)
* `http://v2.www.example.com/dir/other.html`:不同源(域名不同)
* `http://www.example.com:81/dir/other.html`:不同源(端口不同)
如果没有同源策略,浏览器毫无安全可言。
Ajax本身不能跨域,需要借助其他技术实现。
## 4.2 JSONP
jsonp是经典跨域解决方案,对服务区改造小
jsonp是JSON with padding的简写,原理是利用script的src属性不受同源策略限制。(具有src属性的如img,iframe,srcipt都不受同源策略的影响),要点就是允许用户传递一个callback参数给服务器,然后服务器返回数据时会将callback参数作为函数名包裹住json数据,这样客户端可以定制自己的函数来处理返回的数据。
光看上面文字理解不全面,自己动手试验才能深刻理解
### 4.2.1创建跨域环境
首先要创建一个跨域的环境:需要用到本地服务器。推荐[phpstudy](http://www.phpstudy.net/)。不需要繁琐的配置。
打开phpstudy面板-->站点域名管理。网站文件夹都放在了 phpstydy目录下的`X盘:\phpstudy\WWW`下面,我这里默认端口为80。新增一个网站目录为`X盘:\phpstudy\WWW2`,网站端口为1122。 端口不同也算跨域。保存后重启服务器,这样就有2个端口的网站。

### 4.2.2 发送跨域请求
在网页输入`localhost:1122即可打开新端口的网站`,向`localhost:80`的后台文件请求数据
<pre>function addScriptTag(src) {
var script = document.createElement('script');
script.setAttribute("type","text/javascript");
script.src = src;
document.body.appendChild(script);
}
window.onload = function () {
var url = 'http://localhost:80/server.php'
addScriptTag(url+'callback=foo');
}
function foo(data) {
console.log('Your public IP address is: ' + data.ip);
};
</pre>
服务器端返回的数据。以php为例:server.php
<pre>
echo "foo({ip: 1.1.1.2})";
</pre>
注意,开头说的服务器改造,就是后端返回数据时,将JSON数据包含在前后端约定的方法名中、如例子中的`foo`。如果不这么做,前端会虽然能拿到数据,但会报错。`Uncaught SyntaxError: Unexpected token :`
我们可以在客户端的foo方法对客户端返回的数据做处理。
### 4.2.3 JSONP 优缺点
优点:
简单,函数回调在本地处理;
缺点:
1、安全性(存在注入漏洞,如CSRF,XSS);
2、如果出现错误,不会像http请求那样有状态码;
3、只能使用get请求;
### 4.2.4 cros跨域
另外一个常用的可以方法是CROS,墙裂推荐 阅读这篇文章
* [跨域资源共享 CORS 详解 阮一峰](http://www.ruanyifeng.com/blog/2016/04/cors.html)
# 5 封装Ajax和jsonp
## 5.1 代码
<pre>function ajax(params) {
params = params || {};
params.data = params.data || {};
// 判断是ajax请求还是jsonp请求
var json = params.jsonp ? jsonp(params) : json(params);
// ajax请求
function json(params) {
// 请求方式,默认是GET
params.type = (params.type || 'GET').toUpperCase();
// 避免有特殊字符,必须格式化传输数据
params.data = formatParams(params.data);
var xhr = null;
// 实例化XMLHttpRequest对象
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else {
// IE6及其以下版本
xhr = new ActiveXObjcet('Microsoft.XMLHTTP');
}
// 监听事件,只要 readyState 的值变化,就会调用 readystatechange 事件
xhr.onreadystatechange = function () {
// readyState属性表示请求/响应过程的当前活动阶段,4为完成,已经接收到全部响应数据
if (xhr.readyState == 4) {
var status = xhr.status;
// status:响应的HTTP状态码,以2开头的都是成功
if (status >= 200 && status < 300) {
var response = '';
// 判断接受数据的内容类型
var type = xhr.getResponseHeader('Content-type');
if (type.indexOf('xml') !== -1 && xhr.responseXML) {
response = xhr.responseXML; //Document对象响应
} else if (type === 'application/json') {
response = JSON.parse(xhr.responseText); //JSON响应
} else {
response = xhr.responseText; //字符串响应
}
// 成功回调函数
params.success && params.success(response);
} else {
params.error && params.error(status);
}
}
};
// 连接和传输数据
if (params.type == 'GET') {
// 三个参数:请求方式、请求地址(get方式时,传输数据是加在地址后的)、是否异步请求(同步请求的情况极少);
xhr.open(params.type, params.url + '?' + params.data, true);
xhr.send(null);
} else {
xhr.open(params.type, params.url, true);
//必须,设置提交时的内容类型
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
// 传输数据
xhr.send(params.data);
}
}
// jsonp请求
function jsonp(params) {
//创建script标签并加入到页面中
var callbackName = params.jsonp;
var head = document.getElementsByTagName('head')[0];
// 设置传递给后台的回调参数名
params.data['callback'] = callbackName;
var data = formatParams(params.data);
var script = document.createElement('script');
head.appendChild(script);
//创建jsonp回调函数
window[callbackName] = function(json) {
head.removeChild(script);
clearTimeout(script.timer);
window[callbackName] = null;
params.success && params.success(json);
};
//发送请求
script.src = params.url + '?' + data;
//超时处理
if(params.time) {
script.timer = setTimeout(function() {
window[callbackName] = null;
head.removeChild(script);
params.error && params.error({
message: '超时'
});
}, time);
}
};
//格式化参数
function formatParams(data) {
var arr = [];
for (var name in data) {
// encodeURIComponent() :用于对 URI 中的某一部分进行编码
arr.push(encodeURIComponent(name) + '=' + encodeURIComponent(data[name]));
}
// 添加一个随机数参数,防止缓存
arr.push('v=' + random());
return arr.join('&');
}
// 获取随机数
function random() {
return Math.floor(Math.random() * 10000 + 500);
}
}
</pre>
## 5.2 使用
类似于jquery的$.ajax用法,参数接受一个对象
<pre>
ajax({
url: 'test.php', // 请求地址
type: 'GET', // 请求类型,默认"GET",还可以是"POST"
data: {'b': '异步请求'}, // 传输数据
jsonp: '回调方法名'
success: function (res) { // 请求成功的回调函数
console.log(JSON.parse(res));
},
error: function (error) {
} // 请求失败的回调函数
});
</pre>
# 6 参考
[前端必备HTTP技能之Ajax技术详解](http://www.jianshu.com/p/610a05e51fef)
[Ajax原生设计方案](https://github.com/GerryIsWarrior/ajax)
[NDM XMLHttpRequest](https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest)
[Content-type的说明即HTTP请求头的类型整理](http://www.jb51.net/web/85379.html)
[原生JS实现AJAX、JSONP及DOM加载完成事件](http://www.html-js.com/article/1882?utm_source=caibaojian.com)
[跨域原理以及解决方案](http://www.cnblogs.com/bojuetech/p/5895767.html)