本文详细讲述如何使用原生 JS、jQuery 和 Fetch 来实现 AJAX。
AJAX 即 Asynchronous JavaScript and XML,异步的 JavaScript 和 XML。使用 AJAX 可以无刷新地向服务端发送请求接收服务端响应,并更新页面。
原生 JS 实现 AJAX
JS 实现 AJAX 主要基于浏览器提供的 XMLHttpRequest(XHR)类,所有现代浏览器(IE7+、Firefox、Chrome、Safari 以及 Opera)均内建 XMLHttpRequest 对象。
获取XMLHttpRequest对象
// 获取XMLHttpRequest对象
var xhr = new XMLHttpRequest();
如果需要兼容老版本的 IE (IE5, IE6) 浏览器,则可以使用 ActiveX 对象:
var xhr;
if (window.XMLHttpRequest) { // Mozilla, Safari...
xhr = new XMLHttpRequest();
} else if (window.ActiveXObject) { // IE
try {
xhr = new ActiveXObject('Msxml2.XMLHTTP');
} catch (e) {
try {
xhr = new ActiveXObject('Microsoft.XMLHTTP');
} catch (e) {}
发送一个 HTTP 请求
接下来,我们需要打开一个URL,然后发送这个请求。分别要用到 XMLHttpRequest 的 open() 方法和 send() 方法。
// GET
var xhr;
if (window.XMLHttpRequest) { // Mozilla, Safari...
xhr = new XMLHttpRequest();
} else if (window.ActiveXObject) { // IE
try {
xhr = new ActiveXObject('Msxml2.XMLHTTP');
} catch (e) {
try {
xhr = new ActiveXObject('Microsoft.XMLHTTP');
} catch (e) {}
if (xhr) {
xhr.open('GET', '/api?username=admin&password=root', true);
xhr.send(null);
// POST
var xhr;
if (window.XMLHttpRequest) { // Mozilla, Safari...
xhr = new XMLHttpRequest();
} else if (window.ActiveXObject) { // IE
try {
xhr = new ActiveXObject('Msxml2.XMLHTTP');
} catch (e) {
try {
xhr = new ActiveXObject('Microsoft.XMLHTTP');
} catch (e) {}
if (xhr) {
xhr.open('POST', '/api', true);
// 设置 Content-Type 为 application/x-www-form-urlencoded
// 以表单的形式传递数据
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('username=admin&password=root');
open() 方法有三个参数:
HTTP 请求方式。 GET,POST,HEAD 或任何服务器所支持的您想调用的方式。按照HTTP规范,该参数要大写;否则,某些浏览器(如Firefox)可能无法处理请求
请求页面的 URL。由于同源策略(Same origin policy)该页面不能为第三方域名的页面。同时一定要保证在所有的页面中都使用准确的域名,否则调用 open() 会得到 permission denied 的错误提示。
请求是否为异步模式。如果是 TRUE,JavaScript 函数将继续执行,而不等待服务器响应。这就是 AJAX 中的 A。
如果第一个参数是 GET,则可以直接将参数放在 url 后面,如:http://nodejh.com/api?name=admint&password=root
。
如果第一个参数是 POST,则需要将参数写在 send() 方法里面。send() 方法的参数可以是任何想送给服务器的数据。这时数据要以字符串的形式送给服务器,如:name=admint&password=root。或者也可以传递 JSON 格式的数据:
// 设置 Content-Type 为 application/json
xhr.setRequestHeader('Content-Type', 'application/json');
// 传递 JSON 字符串
xhr.send(JSON.stringify({ username:'admin', password:'root' }));
如果不设置请求头,原生 AJAX 会默认使用 Content-Type 是 text/plain;charset=UTF-8 的方式发送数据。
关于 Content-Type 更详细的内容,将在以后的文章中解释说明。
处理服务器的响应
当发送请求时,我们需要指定如何处理服务器的响应,我们需要用到 onreadystatechange 属性来检测服务器的响应状态。使用 onreadystatechange 有两种方式,一是直接 onreadystatechange 属性指定一个可调用的函数名,二是使用一个匿名函数:
// 方法一 指定可调用的函数
xhr.onreadystatechange = onReadyStateChange;
function onReadyStateChange() {
// do something
// 方法二 使用匿名函数
xhr.onreadystatechange = function(){
// do the thing
接下来我们需要在内部利用 readyState 属性来获取当前的状态,当 readyState 的值为 4,就意味着一个完整的服务器响应已经收到了,接下来就可以处理该响应:
// readyState的取值如下
// 0 (未初始化)
// 1 (正在装载)
// 2 (装载完毕)
// 3 (交互中)
// 4 (完成)
if (xhr.readyState === 4) {
// everything is good, the response is received
} else {
// still not ready
完整代码如下:
// POST
var xhr;
if (window.XMLHttpRequest) { // Mozilla, Safari...
xhr = new XMLHttpRequest();
} else if (window.ActiveXObject) { // IE
try {
xhr = new ActiveXObject('Msxml2.XMLHTTP');
} catch (e) {
try {
xhr = new ActiveXObject('Microsoft.XMLHTTP');
} catch (e) {}
if (xhr) {
xhr.onreadystatechange = onReadyStateChange;
xhr.open('POST', '/api', true);
// 设置 Content-Type 为 application/x-www-form-urlencoded
// 以表单的形式传递数据
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('username=admin&password=root');
// onreadystatechange 方法
function onReadyStateChange() {
// 该函数会被调用四次
console.log(xhr.readyState);
if (xhr.readyState === 4) {
// everything is good, the response is received
if (xhr.status === 200) {
console.log(xhr.responseText);
} else {
console.log('There was a problem with the request.');
} else {
// still not ready
console.log('still not ready...');
当然我们可以用onload来代替onreadystatechange等于4的情况,因为onload只在状态为4的时候才被调用,代码如下:
xhr.onload = function () { // 调用onload
if (xhr.status === 200) { // status为200表示请求成功
console.log('执行成功');
} else {
console.log('执行出错');
然而需要注意的是,IE对 onload 属性的支持并不友好。除了 onload 还有以下几个属性也可以用来监测响应状态:
onloadstart
onprogress
onabort
ontimeout
onerror
onloadend
jQuery 实现 AJAX
jQuery 作为一个使用人数最多的库,其 AJAX 很好的封装了原生 AJAX 的代码,在兼容性和易用性方面都做了很大的提高,让 AJAX 的调用变得非常简单。下面便是一段简单的 jQuery 的 AJAX 代码:
$.ajax({
method: 'POST',
url: '/api',
data: { username: 'admin', password: 'root' }
.done(function(msg) {
alert( 'Data Saved: ' + msg );
对比原生 AJAX 的实现,使用 jQuery 就异常简单了。当然我们平时用的最多的,是下面两种更简单的方式:
// GET
$.get('/api', function(res) {
// do something
// POST
var data = {
username: 'admin',
password: 'root'
$.post('/api', data, function(res) {
// do something
Fetch API
Fetch简介
使用 jQuery 虽然可以大大简化 XMLHttpRequest 的使用,但 XMLHttpRequest 本质上但并不是一个设计优良的 API: + 不符合关注分离(Separation of Concerns)的原则 + 配置和调用方式非常混乱 + 使用事件机制来跟踪状态变化 + 基于事件的异步模型没有现代的 Promise,generator/yield,async/await 友好
Fetch API 旨在修正上述缺陷,它提供了与 HTTP 语义相同的 JS 语法,简单来说,它引入了 fetch() 这个实用的方法来获取网络资源。
Fetch 的浏览器兼容图如下:
Fetch API 的原生支持率并不高,幸运的是,引入下面这些 polyfill 后可以完美支持 IE8+:
由于 IE8 是 ES3,需要引入 ES5 的 polyfill: es5-shim, es5-sham
引入 Promise 的 polyfill: es6-promise
引入 fetch 探测库:fetch-detector
引入 fetch 的 polyfill: fetch-ie8
可选:如果你还使用了 jsonp,引入 fetch-jsonp
可选:开启 Babel 的 runtime 模式,现在就使用 async/await
Fetch 使用示例
先看一个简单的 Fetch API 的例子 ? :
fetch('/api').then(function(response) {
return response.json();
}).then(function(data) {
console.log(data);
}).catch(function(error) {
console.log('Oops, error: ', error);
使用 ES6 的箭头函数后:
fetch('/api').then(response => response.json())
.then(data => console.log(data))
.catch(error => console.log('Oops, error: ', error))
可以看出使用Fetch后我们的代码更加简洁和语义化,链式调用的方式也使其更加流畅和清晰。但这种基于 Promise 的写法还是有 Callback 的影子,我们还可以用 async/await 来做最终优化:
async function() {
try {
let response = await fetch(url);
let data = response.json();
console.log(data);
} catch (error) {
console.log('Oops, error: ', error);
使用 await 后,写代码就更跟同步代码一样。await 后面可以跟 Promise 对象,表示等待 Promise resolve() 才会继续向下执行,如果 Promise 被 reject() 或抛出异常则会被外面的 try...catch 捕获。
Promise,generator/yield,await/async 都是现在和未来 JS 解决异步的标准做法,可以完美搭配使用。这也是使用标准 Promise 一大好处。
使用 Fetch 的注意事项
Fetch 请求默认不会发送和接收 cookie,如果需要支持 cookie,需要设置 fetch(url, {credentials: 'include'})`
服务器返回 4xx,5xx 错误码时,从 fetch 返回的 Promise 不会被标记为 reject(但是会将 resolve 的返回值的 ok 属性设置为 false),只有网络错误这些导致请求不能完成时,fetch 才会被 reject
接下来将上面基于 XMLHttpRequest 的 AJAX 用 Fetch 改写:
var options = {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
body: JSON.stringify({ username: 'admin', password: 'root' }),
credentials: 'include'
fetch('/api', options).then(response => response.json())
.then(data => console.log(data))
.catch(error => console.log('Oops, error: ', error))
Axios
Axios 是一个基于Promise 用于浏览器和 nodejs 的 HTTP 客户端,本质上也是对原生XHR的封装,只不过它是Promise的实现版本,符合最新的ES规范,相比于Fetch,Axios的api与jQuery很接近,更简单易用,其兼容性相较于Fetch也更好,可以兼容到IE7,在node环境下和在浏览器中使用的api一致,通用性也很好,根据其官网的描述,其还具有以下特点:
从node.js创建http请求
支持Promise API
客户端支持防止CSRF
提供了一些并发请求的接口
Axios会自动将数据转换成JSON格式,不用再手动转换
axios({
method: 'post',
url: '/user/12345',
data: {
firstName: 'Fred',
lastName: 'Flintstone'
.then(function (response) {
console.log(response);
.catch(function (error) {
console.log(error);
以下示例代码展示了Axios与Fetch在处理请求时的简单区别
// fetch
fetch("url")
.then((response) => response.json())
.then((data) => console.log(data))
.catch((error) => console.log(error));
// axios
axios
.get("url")
.then((response) => console.log(response))
.catch((error) => console.log(error));
从以上示例代码中可知,使用 Axios 时不用额外的将数据转换为JSON格式,在 fetch 的例子中,必须将数据转换为 JSON 格式
除以上特点之外,Axios还能很方便的进行请求拦截的配置,这也是他的关键功能之一,它可以用来实现一些通用信息的设置或者数据的初始化等等,示例如下:
// 请求拦截
axios.interceptors.request.use((config) => {
console.log("Request sent");
// 响应拦截
axios.interceptors.response.use((response) => {
// do an operation on response
return response;
axios
.get("url")
.then((response) => console.log(response))
.catch((error) => console.log(error));
在以上示例代码中,可以看到请求拦截和响应拦截。在第一种情况下,创建了一个 console.log,告知发送请求的情况,在响应拦截中,可以对响应做任何操作,然后返回
关于Ajax的特别说明
Ajax只是利用脚本访问对应url获取数据而已,Ajax不会做出了获取数据之外的其他动作,它与普通的http请求有如下区别
正常的http url请求,只有浏览器和服务器两个参与者。浏览器端发起一个http请求,服务器端处理后发起一个重定向,浏览器端从response中获取到重定向地址,发起另一个http url请求。也就是说,浏览器会按照response中的内容进行响应(如重定向),这是由浏览器的功能决定的。
如果发起ajax请求,参与者就有三个,即ajax、客户端、服务器,ajax处于客户端和服务器两者之间。过程是客户端发起一个ajax请求,服务器端处理后,如果发起一个重定向,ajax只会获取刚才请求返回的数据(如果发起了页面重定向,则会获取到重定向后的页面的文本内容),其他的任何动作一概不去做,ajax是这么做的。也就是说,引入了ajax之后,ajax就插在浏览器和服务器之间了,服务器给浏览器的response被ajax拦截了,但是ajax本身却什么都不做,也不转达。
Ajax实际上是通过XMLHttpRequest来向服务器发送异步请求的,从服务器获取数据,然后使用JS来更新页面,这也就是常说的局部刷新实现方式,所以Ajax请求之后,服务器返回的都是纯文本流,客户端的浏览器在获取Ajax异步结果时,不是直接显示在页面上,而是要通过JS来进行处理,JS处理完以后才能显示在页面上
解决Ajax请求重定向的问题
后端的处理方式
@GetMapping("/enable")
public String enable() {
return "login/enable";
// 对于请求是ajax请求重定向问题的处理方法
@PostMapping("/saveUser")
public void verify(HttpServletRequest request, HttpServletResponse response) throws IOException{
// doSomething...
String type = request.getHeader("X-Requested-With");
if ("XMLHttpRequest".equals(type)) {
// 此处使用直接给response设置值得做法可以保持返回结果的统一,如果方法存在返回值,也可以直接将路径返回到前端
// 当将路径返回前端时,需要在controller方法或者类上加上@ResponseBody注解
// 处理AJAX请求,设置响应头信息
response.setHeader("REDIRECT", "REDIRECT");
// 需要跳转页面的URL
response.setHeader("CONTEXTPATH", "/account/enable");
} else {
//其他的http请求,直接重定向就可以了
response.sendRedirect("/account/enable");
前端处理方式
function register() {
$.ajax({
type: "POST",
dataType: "",
url: "/account/saveUser",
data: $('#form1').serialize(),
complete: function (XMLHttpRequest) {
// 通过xhr取得响应头
const REDIRECT = XMLHttpRequest.getResponseHeader("REDIRECT");
//如果响应头中包含 REDIRECT 则说明是我们需要进行重定向的
if (REDIRECT == "REDIRECT") {
window.location.href = XMLHttpRequest.getResponseHeader("CONTEXTPATH");
后端携带参数进行重定向
携带参数重定向主要是通过使用RedirectAttributes实现的,RedirectAttributes是Spring mvc 3.1版本之后出来的一个功能,专门用于重定向之后还能带参数跳转的。它有如下两种带参的方式:
方式一:attr.addAttribute("param", value);
这种方式就相当于重定向之后,在url后面拼接参数,这样在重定向之后的页面或者控制器再去获取url后面的参数就可以了,但这个方式因为是在url后面添加参数的方式,所以暴露了参数,有风险
attr.addAttribute("name", "123");
attr.addAttribute("success", "success");
return "redirect:/index";
// 相当于:return "redirect:/index?name=123&success=success"
方式二:attr.addFlashAttribute("param", value);
这种方式也能达到重新向带参,而且能隐藏参数,其原理就是放到session中,session在跳到页面后马上移除对象。所以刷新一下后这个值就会丢掉
attr.addFlashAttribute("status","999");
attr.addFlashAttribute("message","登录失败");
return "redirect:/toLogin";
@RequestMapping("/zh")
public String reZh(RedirectAttributes attr){
attr.addAttribute("time","ssssss");
attr.addFlashAttribute("hi","hello");
return "redirect:/date?time={time}";
@GetMapping("/date")
public String toDate(@RequestParam(value = "time",required = false)String s,HttpServletRequest request){
Object hi = RequestContextUtils.getInputFlashMap(request).get("hi");
System.out.println(hi);
return s;
AJAX 之 XHR, jQuery, Fetch 的对比
Fetch还是Axios——哪个更适合HTTP请求?
Ajax发送Post请求,后端控制器使用重定向页面没有自动跳转