添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
呐喊的竹笋  ·  yii-xunsearch | ...·  2 月前    · 
小胡子的围巾  ·  本科生培养·  3 月前    · 

本文详细讲述如何使用原生 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请求,后端控制器使用重定向页面没有自动跳转
  •