# AJAX 基础

TIP

结合 Postman 验证本章节内容。

# 1. 基本概念

传统 Web 应用的缺点:

  • 独占式的请求

  • 频繁的页面刷新

当下的前端开发通常都会借助 Ajax 技术,Ajax 并没有太多新的内容,但 Ajax 丰富了前端开发的功能。

Ajax 技术的核心概念就是两个:『异步』和『局部刷新』。

AJAX 的全称是 Asynchronous JavaScript XML(异步 JavaScript 和 XML),从 AJAX 的组合名称可以看出 AJAX 其实并不是一种技术,而是多种技术的组合,每种技术都有其独特之处,合在一起就成了功能强大的技术。AJAX 的出现揭开了无刷新页面的新时代。

利用 AJAX 技术,Web 前端只需要在后台与服务器进行少量数据交换。

AJAX 采用了异步交互的方式,从而改变了同步交互过程中的“请求 - 等待 - 请求 - 等待”的模式。

异步,是指基于 Ajax 的应用与服务器通信的方式。对于

  • 传统的 Web 应用,每次用户发送请求,向服务器请求获得新数据时,浏览器都会完全丢弃当前页面,而等待重新加载新的页面。而在服务器完全响应之前,用户浏览器将一片空白,用户的动作必须中断。而
  • 异步是指用户发送请求后,无须等待,请求在后台发送,不会阻塞用户当前活动。用户无须等待第一次请求得到完全响应,即可发送第二次请求。

简单来说,AJAX 的工作原理是通过 xmlHttpRequest 对象来向服务器发出异步请求。xmlHttprequest 可以同步或异步返回 Web 服务器的响应,并且能以文本或一个 DOM 文档形式返回内容。

普通的 Web 项目的工作流程是:

ajax_1

TIP

  1. 发起 <请求-1>
  2. 获得 <页面-1>
  3. 发起 <请求-2>
  4. 获得 <页面-2>
  5. 发起 <请求-3>
  6. 获得 <页面-3>
  7. ...

Ajax 的 Web 项目的流程是:

ajax_2

TIP

  1. 发起 <请求-1>
  2. 获得 <页面-1>
  3. 发起 <请求-2>
  4. 获得 数据,修改 <页面-1>
  5. 发起 <请求-3>
  6. 获得 数据,修改 <页面-1>
  7. ...

AJAX 的核心是 XMLHttpRequest 对象(首次出现于 IE5,如今已被 HTML5 制定为正式规范。)。XMLHttpRequest 提供了异步通信的能力,通过它浏览器可以向服务器发送异步的请求,也可通过它读取服务器响应。

JavaScript 主要完成 AJAX 如下事情:

  • 创建 XMLHttpRequest 对象;

  • 通过 XMLHttpRequest 对象向服务器发送请求;

  • 创建回调函数,监视服务器响应状态,在服务器响应完成后,回调函数启动;

  • 回调函数通过 DOM 动态更新 HTML 页面;

var request = new XMLHttpRequest(); // 创建XMLHttpRequest对象

// ajax 是异步的,设置回调函数
request.onreadystatechange = function () { // 状态发生变化时,函数被回调
    if (request.readyState === 4) { // 成功完成
        // 判断响应状态码
        if (request.status === 200) {
            // 成功,通过 responseText 拿到响应的文本:
            return success(request.responseText);
        } else {
            // 失败,根据响应码判断失败原因:
            return fail(request.status);
        }
    } else {
        /* HTTP 请求还在继续...
         0: 请求未初始化
         1: 服务器连接已建立
         2: 请求已接收
         3: 请求处理中
        */
    }
}

// 发送请求:
request.open('GET', '/api/categories');
request.setRequestHeader("Content-Type", "application/json") //设置请求头
request.send(); //到这一步,请求才正式发出

# 2. HTTP 请求的 content-type

HTTP 请求的请求头中会有一个较为重要的键值对:content-type,它的值常见有 2 个:application/x-www-form-urlencodedapplication/json

HTTP 请求的 content-type 决定了:本次请求所提交的参数(例如,登录请求所携带的用户名和密码信息)Query String 格式,还是 JSON String 格式。

警告

HTTP 请求提交的参数数据,无论是 Query String 格式,还是 JSON String 格式,都只是格式的不同,其数据内容是完全一致的。

就像同样一句话,(在不考虑同音字引起歧义的情况下)我用拼音写,还是用汉字写,本质上并没有区别,两种写法传递的信息,表达的含义是一样的,它们只是『写法不同』而已。

注意

再次强调一遍,上述的两种格式,显而易见,只是格式的不同。它俩表达的含义、传递的数据本质上是一样的!

# 3. HTTP 响应的 content-type

HTTP 响应的响应头中也有一个 content-type 键值对,它的值我们常见两种:

# content-type
1 text/html
2 application/json

HTTP 响应头中的 content-type 决定了:(通过 Tomcat)回给浏览器的数据的格式类型:

  • 如果,你(通过 Tomcat)回给浏览器的数据是一个 HTML 格式的字符串,那么,本次请 HTTP 响应的 content-type 的值就应该是 text/html 与之呼应;

  • 如果,你(通过 Tomcat)回给浏览器的数据是一个 JSON 格式的字符串,那么,本次请 HTTP 响应的 content-type 的值就应该是 application/json 与之呼应;

# 4. AJAX 请求和 content-type

当前浏览器发出的是否是 AJAX 请求与 content-type 无关,而是与是否使用了 JavaScript 的 XMLHttpRequest 有关。

所以一个常见的『标准错误答案』是:HTTP 请求头的 content-type 的值为 application/json ,则意味着当前请求是 AJAX 请求。这个说法是错的!

注意

“当前请求是一个 AJAX 请求,还是一个普通请求?当前请求是一个传递 query-string 参数的请求,还是一个传递 json-string 参数的请求?” 这是 2 个独立的问题!

就好像你去思考 “这个人是一个男人,还是一个女人?ta 是一个好人,还是一个坏人?” 一样。如果你直接回答:因为他是男人,所以他是个好人 。那么,很显然别人就会觉得你这个人脑子不好使。

正确的观点应该是:AJAX 请求只和 XMLHttpRequest 对象有关。通过 XMLHttpRequest 发出的请求就是 AJAX 请求,反之则不是。

# 参数风格 content-type Servlet 获取参数
普通请求一 query-string 格式 application/x-www-form-urlencoded request.getParameter()
普通请求二 json-string 格式 application/json request.getReader()
AJAX 请求一 query-string 格式 application/x-www-form-urlencoded request.getParameter()
AJAX 请求二 json-string 格式 application/json request.getReader()

其中,普通请求二 比较少见。

# 5. application/json 和 request.getReader()

由于 Servlet 中的 request.getParameter("...") 只对 content-type 值为 application/x-www-form-urlencoded 的情况有效有效。

当你的请求参数风格是 json-string 风格,即,HTTP 请求头的c ontent-type 值为 application/json 时,你的 Servlet 中的 request.getParameter("...") 方法的值为 null,是获取不到页面提交的参数的。

这种情况下,你需要自己“想办法”从 HTTP 请求的 Body 中,将请求参数取出来:

BufferedReader br = request.getReader();

String str, wholeStr = "";
while ((str = br.readLine()) != null) {
    wholeStr += str;
}
System.out.println(wholeStr);

# 6. 人造『奇葩』请求

下面几种请求方式,在我们通过浏览器向后台发出 HTTP 请求时,是无法出现的,浏览器不会组装出这样的 HTTP 请求。它们都是我们通过 postman 这样的客户端工具(或其它方式)人为『造』出来的『奇葩』情况。

这些请求都是属于『看起来合乎 HTTP 规则要求,但是大家都不会这么用』的情况。

# 人造奇葩请求一

  • 参数格式为 query-string 风格,但是 HTTP 请求头的 content-type 值为 application/json;

  • 参数格式为 json-string 风格,但是 HTTP 请求头的 content-type 值为 application/x-www-form-urlencoded。

上面两种都是属于:我口头告诉你我给你的是个苹果,但是实际上我手里递给你的是个梨。

虽然看似没有报错,它俩都属于逻辑上的 bug,本不应该出现这样的情况。

# 人造“奇葩”请求二

发出 GET 请求,提交的参数是 json-string 风格,放在 HTTP 请求体中,HTTP 请求头中 content-type 值为 application/json 。

上诉请求的“奇葩”之处在于:

  1. 正常情况下,Get 请求的请求参数一般都是 query-string 风格,而不是 json-string;

  2. 正常情况下,Get 请求的请求参数都是拼接在 URL 后面,写在 HTTP 请求行中的,而不是在请求体中;

  3. 正常情况下,Get 请求由于请求体中无内容,它的 HTTP 请求头中是没有 content-type 键值对的。

相较于 奇葩请求一 而言,这样的请求不算太奇葩,因为,它不是一种错,只是少见。大家更习惯于 get 的参数以 query-string 的格式拼接在 URL 后面,放在 HTTP 请求头中,HTTP 请求体空着。

有可能你会遇到这种请求,它并非特别“奇葩”、罕见。

# 人造奇葩请求三

发出 POST 请求,提交的参数是 query-string,拼接在 URL 后面,放在 HTTP 请求行中。由于 HTTP 请求体中是空的,所有 HTTP 请求头中没有 content-type 键值对。

这种请求的奇葩之处在于:既然你想这么干,为什么不用 get 请求?

『完』