Skip to content

AJAX 技术

第一章:初探 AJAX

一、SSR & CSR

后端渲染 SSR

服务器端将数据填充到 HTML 模板中,生成完整的 HTML 页面,然后将这个页面发送给客户端的浏览器。浏览器接收到完整的页面后,可以直接显示给用户,而不需要再去执行 JavaScript 来生成页面内容。

前端渲染 CSR

浏览器下载服务器提供的最小的 HTML 文件、JavaScript 文件和 CSS 文件,然后运行 JavaScript 来生成页面的内容。数据通常是通过 XHR 或 Fetch API 从服务器获取的。(➡ 前后端分离) 。

在前端渲染中,服务器的工作主要是提供 API 接口,返回 JSON 数据,而页面的渲染工作主要由客户端(浏览器)完成。这种方式的优点是可以减轻服务器的压力,提高用户交互的响应速度,因为页面的更新是通过 JavaScript 在浏览器端完成的,不需要向服务器发送请求。

缺点是不利于 SEO。

SSG:Static Site Generator(静态网站生成器)

静态网站生成器是一种工具,它可以将源文件(通常是 Markdown 文件或其他轻量级的标记语言文件)转换为静态 HTML 文件。这些静态 HTML 文件可以直接部署到任何静态文件服务器上,无需任何后端服务器支持。

常见的静态网站生成器包括 Jekyll、Hexo、Hugo、Gatsby 等。

二、是什么?

AJAX 是 Asynchronous JavaScript and XML(异步 JavaScript 和 XML)的缩写,是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。

AJAX 不是一种新的编程语言,而是一种使用现有标准和技术的新方法。AJAX 基于 JavaScript 和 HTTP 请求(XMLHttpRequest 对象),可以实现在后台与服务器进行数据交换,这使得网页能够异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。

第二章:XHR

一、走进 XHR

1. 发展历史

1999年: XMLHttpRequest 最初是由 Microsoft 在 1999 年作为 Internet Explorer 5 的一部分引入的。它是作为一个 ActiveX 对象实现的,名为 Microsoft.XMLHTTPvar xhr = new ActiveXObject("Microsoft.XMLHTTP");)。

2000年代初: 随后,Mozilla、Safari 和 Opera 等其他浏览器也实现了这个对象,使得它成为了一个事实上的标准。

2006年: Google 在其 Gmail 和 Google Maps 等产品中大量使用 XMLHttpRequest,这进一步推动了它的普及。这也是 AJAX(Asynchronous JavaScript and XML)这个术语开始广泛使用的时候,AJAX 描述的就是使用 XMLHttpRequest 进行异步通信的技术。

2011年: W3C 和 WHATWG 开始制定 XMLHttpRequest 的标准,以确保所有的浏览器都能以相同的方式实现它。

至今: XMLHttpRequest 现在已经成为了 Web 开发的基础,尽管新的 API,如 Fetch API,已经开始提供更现代、更强大的功能,但 XMLHttpRequest 仍然在许多场景中被广泛使用。

2. 快速入门

第一步:创建网络请求的 AJAX 对象(使用 XMLHttpRequest)。

第二步:监听 XMLHttpRequest 对象状态的变化,或者监听 onload 事件(请求完成时触发)。

第三步:配置网络请求(通过 open 方法)。

第四步:发送 send 网络请求。

javascript
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
  if (xhr.readyState == 4 && xhr.status == 200)
    console.log(xhr.responseText);
};
/**
 * open(method, url, async)
 * 发送同步请求时将 open 的第三个参数设置为 false。可选,默认为 true
 **/
xhr.open('GET', 'https://api.example.com/data', true);
// open() 方法只是初始化请求,实际的网络请求还没有发出。网络请求会在调用 send() 方法时发出。
xhr.send();

二、XHR 细节

1. XHR 状态

在一次网络请求中可以通过 xhr.readyState 看到状态发生了很多次变化,这是因为对于一次请求来说包括如下的状态:

状态描述
0UNSENT代理被创建,但尚未调用 open() 方法。
1OPENEDopen() 方法已经被调用。
2HEADERS_RECEIVEDsend() 方法已经被调用,并且响应头已经可获得。
3LOADING下载中;responseText 属性已经包含部分数据。
4DONE下载操作已完成。

注意:这个状态并非是 HTTP 的相应状态,而是记录的 XMLHttpRequest 对象的状态变化。http 响应状态通过 status 获取。

2. 其他事件监听

除了 onreadystatechange 还有其他的事件可以监听。

  • loadstart:请求开始。

  • progress:一个响应数据包到达。此回调函数通常无法直接访问到已经接收到的响应体内容。这是因为响应体内容通常只在请求完成(即 readyState 为 4)时才可用。在 onprogress 事件处理函数中,通常只能访问到已经接收到的字节数和预期要接收的总字节数。

    ProgressEvent 对象有以下属性:

    • lengthComputable:一个布尔值,表示进度是否可以被计算。如果总字节数已知,则该值为 true,否则为 false。
    • loaded:已经接收到的字节数。
    • total:预期接收的总字节数。
    javascript
    var xhr = new XMLHttpRequest();
    xhr.open('GET', 'https://example.com/large-file', true);
    
    xhr.onprogress = function (event) {
      if (event.lengthComputable) {
        var percentComplete = event.loaded / event.total * 100;
        console.log('下载进度:' + percentComplete + '%');
      }
    };
    
    xhr.onload = function () {
      if (xhr.status == 200) {
        console.log('下载完成');
      } else {
        console.log('下载失败,HTTP 状态码:' + xhr.status);
      }
    };
    
    xhr.send();
  • abort:调用 xhr.abort() 取消一个请求时,XMLHttpRequest 对象的 onabort 事件处理器会被调用,可以在这个处理器中进行一些清理工作。

  • error:发生连接错误,例如域错误(域名解析)。不会发生诸如 404 这类的 HTTP 错误,如 404 会触发 load 事件。

  • load:请求成功完成。

  • timeout:由于请求超时而取消了该请求(仅发生在设置了 timeout 的情况下)。

  • loadend:在 load、error、timeout 或 abort 之后触发。也就是当请求结束(无论成功或失败)时触发。

注意:也可以使用 load 来获取数据。

3. HTTP 响应的状态 status

XMLHttpRequest 的 state 是用于记录 xhr 对象本身的状态变化,并非针对于 HTTP 的网络请求状态。

如果我们希望获取 HTTP 响应的网络状态,可以通过 status 和 statusText 来获取:

  • status:这个属性返回一个数字,表示 HTTP 响应的状态码。例如,200 表示请求成功,404 表示请求的资源未找到。
  • statusText:这个属性返回一个字符串,表示 HTTP 响应的状态信息。例如,当 status 是 200 时,statusText 通常是 "OK";当 status 是 404 时,statusText 通常是 "Not Found"。

4. 响应数据和响应类型

发送了请求后,需要获取对应的结果:response 属性。

返回的类型取决于 responseType 的属性设置。通过 responseType 可以设置获取数据的类型。

如果将 responseType 的值设置为空字符串,则会使用 text 作为默认值。

javascript
// 1. 创建网络请求的 AJAX 对象
const xhr = new XMLHttpRequest()

// 2. onload 监听数据加载完成
xhr.onload = function() {
  // 如果 responseType = "text" 或者不设置,那么 xhr.response 是字符串,需要解析为 JSON
  // const resJSON = JSON.parse(xhr.response)
  // 如果 responseType = "json",xhr.response 返回对象
  console.log(xhr.response)
  // console.log(xhr.responseText)
  // console.log(xhr.responseXML)
}

// 3. 告知 xhr 获取到的数据的类型
xhr.responseType = "json"
// xhr.responseType = "xml"
// xhr.responseType = "text"

// 4. 配置网络请求
// 4.1. json类型的接口
xhr.open("get", "http://123.207.32.32:1888/01_basic/hello_json")
// 4.2. text类型的接口
// xhr.open("get", "http://123.207.32.32:1888/01_basic/hello_text")
// 4.3. xml类型的接口
// xhr.open("get", "http://123.207.32.32:1888/01_basic/hello_xml")

// 5. 发送网络请求
xhr.send()

xhr.responsexhr.responseXML 都是 XMLHttpRequest 对象的属性,用于获取服务器的响应数据,但它们的数据类型和使用场景有所不同。

  • xhr.response:这个属性返回服务器的响应数据。这个数据的类型取决于 xhr.responseType 的值。例如,如果 xhr.responseType 是 "text"(默认值),那么 xhr.response 将返回一个字符串。如果 xhr.responseType 是 "json",那么 xhr.response 将返回一个 JavaScript 对象。
  • xhr.responseXML:这个属性返回一个 Document 对象,这个对象代表服务器返回的 XML 数据。这个属性只有在服务器返回的数据类型是 "text/xml" 或 "application/xml" 时才可用。如果服务器返回的数据不是有效的 XML,那么这个属性的值将为 null。

所以,如果你的服务器返回的是 XML 数据,你可以使用 xhr.responseXML 来获取这个数据。如果服务器返回的是其他类型的数据(如 JSON、文本等),你应该使用 xhr.response 来获取这个数据。

5. GET / POST 请求传递参数

编写 HTML 页面。

html
<form class="info">
  <input type="text" name="username">
  <input type="password" name="password">
</form>
<button class="send">发送请求</button>

使用 JavaScript AJAX 发起网络请求。

javascript
const formEl = document.querySelector(".info")
const sendBtn = document.querySelector(".send")

sendBtn.onclick = function() {
  // 创建xhr对象
  const xhr = new XMLHttpRequest()

  // 监听数据响应
  xhr.onload = function() {
	console.log(xhr.response)
  }

  // 配置请求
  xhr.responseType = "json"

  // 1.传递参数方式一: get -> query
  // xhr.open("get", "http://123.207.32.32:1888/02_param/get?name=why&age=18&address=广州市")

  // 2.传递参数方式二: post -> urlencoded
  // xhr.open("post", "http://123.207.32.32:1888/02_param/posturl")
  // // 发送请求(请求体body)
  // xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
  // xhr.send("name=why&age=18&address=广州市")

  // 3.传递参数方式三: post -> formdata
  // 默认 Content-type 为 formdata,所以不用设置
  // xhr.open("post", "http://123.207.32.32:1888/02_param/postform")
  // // formElement对象转成FormData对象
  // const formData = new FormData(formEl)
  // xhr.send(formData)

  // 4.传递参数方式四: post -> json
  xhr.open("post", "http://123.207.32.32:1888/02_param/postjson")
  xhr.setRequestHeader("Content-type", "application/json")
  xhr.send(JSON.stringify({name: "why", age: 18, height: 1.88}))
}

三、XHR 网络请求封装

javascript
function ddfajax({
  url,
  method = "get",
  data = {},
  timeout = 10000,
  headers = {}, // token
} = {}) {
  // 1.创建对象
  const xhr = new XMLHttpRequest()

  // 2.创建Promise
  const promise = new Promise((resolve, reject) => {

    // 2.监听数据
    xhr.onload = function() {
      if (xhr.status >= 200 && xhr.status < 300) {
        resolve(xhr.response)
      } else {
        reject({ status: xhr.status, message: xhr.statusText })
      }
    }

    // 3.设置类型
    xhr.responseType = "json"
    xhr.timeout = timeout

    // 4.open方法
    if (method.toUpperCase() === "GET") {
      const queryStrings = []
      for (const key in data) {
        queryStrings.push(`${key}=${data[key]}`)
      }
      url = url + "?" + queryStrings.join("&")
      xhr.open(method, url)
      xhr.send()
    } else {
      xhr.open(method, url)
      xhr.setRequestHeader("Content-type", "application/json")
      xhr.send(JSON.stringify(data))
    }
  })

  // 这样在外部就可以通过 xhr 属性获取到 xhr 对象,进行例如取消操作
  promise.xhr = xhr

  return promise
}

使用封装好的工具。

javascript
const promise = ddfajax({
  url: "http://123.207.32.32:1888/02_param/get",
  data: {
	username: "coder",
	password: "123456"
  }
})

promise.then(res => {
  console.log("res:", res)
}).catch(err => {
  console.log("err:", err)
})

第三章:fetch

思考: 以前开发中如何向服务器请求数据的?

  • 方法一:通过 Ajax 向服务器请求数据,而 Ajax 本质就是使用 XMLHttpRequest 对象实现的。

    可以实现,但是代码写起来很麻烦。

    javascript
    // 1、创建一个xhr对象
    let xhr = new XMLHttpRequest()
    // 2、监听load事件获取响应结果
    xhr.addEventListener('load', function() {
      console.log(JSON.parse(xhr.response))
    })
    // 3、设置请求方式和请求地址
    xhr.open('get', 'http://ajax-base-api-t.itheima.net/api/getbooks?name=zs&age=18')
    // 4.发送请求
    xhr.send()
  • 方法二:通过 axios 实现的代码精简不少,但是 axios 底层仍然是基于 XMLHttpRequest 对象实现的,本质不变,只是进行了 promise 封装。

那么目前除了使用 XMLHttpRequest 发送请求之外,还有没有其他方式呢?

  • 有,就是 fetch。

一、走进 fetch

1. 什么是 fetch?

Fetch API 并不是 ES6(也称为 ECMAScript 2015)的一部分,它是 Web API 的一部分,由 W3C 和 WHATWG 组织定义。Fetch API 首次出现在 2015 年的 HTML5 规范中。

虽然 Fetch API 和 ES6 都在大约相同的时间出现,但它们是两个不同的规范。ES6 是 JavaScript 语言的规范,定义了 JavaScript 的语法和新特性。而 Fetch API 是定义在浏览器环境中的,用于进行网络请求的接口。

需要注意的是,Fetch API 使用了 Promise,这是 ES6 引入的一个新特性。所以,虽然 Fetch API 本身不是 ES6 的一部分,但它的使用依赖于 ES6 的一些特性。

fetch 特性

  • Fetch 被称之为下一代 Ajax 技术,内部采用 Promise 方式来处理数据。

  • API 语法简洁明了,比 XMLHttpRequest 更加简单易用。

  • 采用了模块化设计,API 分散于多个对象中(如:Response 对象、Request 对象、Header 对象)。

  • 通过数据流(Stream 对象)处理数据,可以分块读取,有利于提高网站性能,对于大文件或者网速慢的场景极为有用。

兼容性(浏览器支持程度如何?)

最新统计(下图来自 caniuse):fetch 可以支持到 97.8% 的用户端,除了 IE,主流浏览器都已兼容。

注意:不兼容 IE。

2. Ajax、Fetch、Axios 区别

Ajax 是一个技术统称,是一个概念模型,它囊括了很多技术,并不特指某一技术,它很重要的特性之一就是让页面实现局部刷新。

Fetch 是在 ES6 出现的,它使用了 ES6 提出的 promise 对象。它是 XMLHttpRequest 的替代品。Fetch 是一个 API,它是真实存在的,它是基于 promise 的。

Axios 是随着 Vue 的兴起而被广泛使用的,目前来说,绝大多数的 Vue 项目中的网络请求都是利用 Axios 发起的。当然它并不是一个思想,或者一个原生 API,它是一个封装库。Axios 是一个基于 promise 封装的网络请求库,它是基于 XHR 进行二次封装。

二、fetch 如何使用

【文档推荐】

1. 使用fetch发送基本get请求

介绍:

  • 如果 fetch() 只接收了一个 url 字符串参数,表示默认向该网址发送 get 请求,会返回一个 Promise 对象。
  • 如果需要设置 get 的参数,直接拼接到 url 地址上即可。

语法:

javascript
fetch(url)
  .then(...)
  .catch(...)
1)发送 get 请求
javascript
// 接口地址:http://ajax-base-api-t.itheima.net/api/getbooks
// 请求方式:get
// 查询参数(可选):
//   1、id:需要查询的图书id

fetch('http://ajax-base-api-t.itheima.net/api/getbooks').then(res => {
  // 得到的res,是一个Response对象,需要通过特定方法获取其中内容
  // console.log(res)

  // res.json() 是一个异步操作 (返回的也是 Promise),表示取出所有的内容,将其转换成JSON对象
  return res.json()
}).then(json => {
  // 获取响应体里的数据,返回的是对象
  console.log(json)
}).catch(err => {
  console.log(err)
})
2)get 请求-async/await改写
javascript
// 接口地址:http://ajax-base-api-t.itheima.net/api/getbooks
// 请求方式:get
// 查询参数(可选):
//   1、id:需要查询的图书id

// 把代码封装成async异步函数
async function getData() {
  // 通过try...catch语法处理async-await成功和失败的情况
  try {
    // 先获取Response对象
    let res = await fetch('http://ajax-base-api-t.itheima.net/api/getbooks')
    console.log(res)
    // 通过res.json() 取出response对象中的数据
    let json = await res.json()
    console.log(json)
  } catch(err) {
    // 捕获错误信息
    console.log(err)
  }
}
getData()
3)get请求-async/await改写-添加查询参数
javascript
// 如果需要通过get请求设置查询参数如何实现?
// 可以通过地址栏拼接查询参数完成
async function getData() {
  // 通过try...catch语法处理async-await成功和失败的情况
  try {
    // 先获取Response对象
    let res = await fetch('http://ajax-base-api-t.itheima.net/api/getbooks?id=1')
    console.log(res)
    // 通过res.json() 取出response对象中的数据
    let json = await res.json()
    console.log(json)
  } catch (err) {
    // 捕获错误信息
    console.log(err)
  }
}
getData()

2. Response 对象

fetch 请求成功以后,得到的是一个 Response 对象。它是对应服务器的 HTTP 响应。

javascript
const res = await fetch(url)
console.log(res)
1)常见属性
属性含义
res.ok返回一个布尔类型,表示请求是否成功
res.status返回一个数字,表示 HTTP 回应的状态码(例如:200,表示以请求成功)
res.statusText返回状态的文本信息(例如:请求成功之后,服务器返回 ok)
res.url返回请求的 url 地址
2)常见方法

Response 对象根据服务器返回的不同类型的数据,提供了不同的读取方法。

其中最常用的就是 res.json()

方法含义
res.json()得到 JSON 对象,返回 promise 对象
res.text()得到文本字符串
res.blob()得到二进制 Blob 对象
res.formData()得到 FormData 表单对象
res.arrayBuffer()得到二进制 ArrayBuffer 对象

三、fetch 配置参数

1. 是什么

fetch 的第一个参数是 url,此外还可以接收第二个参数,作为配置对象,可以自定义发出的 HTTP 请求。

比如:fetch(url,options)

其中 post、put、patch 用法类似,以 post 为例演示。

配置参数介绍:

javascript
fetch(url,{
  method: '请求方式,比如:post、delete、put',
  headers:{
    'Content-Type': '数据格式'
  },
  body: 'post请求体数据'
})

2. 发送 post 请求

基本语法1: json格式(常用)

javascript
// 测试接口(新增操作):
// 接口地址:http://ajax-base-api-t.itheima.net/api/addbook
// 请求方式:post
// 请求体参数:
//  1、书名:bookname
//  2、作者:author
//  3、出版社:publisher

// post发送:json格式
async function add() {
  let obj = {
    bookname: '魔法书之如何快速学好前端',
    author: '茵蒂克丝',
    publisher: '格兰芬多'
  }

  let res = await fetch('http://ajax-base-api-t.itheima.net/api/addbook', {
    method: 'post',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(obj)
  })

  let json = await res.json()
  console.log(json)
}
add()

基本语法2: x-www-form-urlencoded 格式(了解)

javascript
async function add1() {
  let res = await fetch(url, {
    method: 'post',
    headers: {
      "Content-type": "application/x-www-form-urlencoded; charset=UTF-8",
    },
    body: 'foo=bar&lorem=ipsum',
  });

  let json = await res.json();
  console.log(json)
}
add1()

基本语法3: formData 格式(了解)

javascript
let form = document.querySelector('form');

async function add2() {
  let res = await fetch(url, {
    method: 'POST',
    body: new FormData(form)
  })

  let json = await res.json()
  console.log(json)
}
add2()

四、fetch 函数封装

原生 fetch 虽然已经支持 promise 了,相比 XMLHttpRequest 已然好用了很多,但是参数还是需要自己处理,比较麻烦。

比如:

  • get、delete 的请求参数,要在地址栏拼接。
  • put、patch、post 的请求参数,要转 json 设置请求头。

所以实际工作,我们还是会对 fetch 二次封装。

目标效果:

javascript
// 发送get请求、delete请求
http({
  method:'xxx'
  url:'xxx',
  params:{......}
})

// 发送post请求、put请求、patch请求
http({
  method:'xxx'
  url:'xxx',
  data:{......}
})

封装之后代码如下:

javascript
async function http(obj) {
  // 解构赋值
  let { method, url, params, data } = obj

  // 判断是否有params参数
  // 1、如果有params参数,则把params对象转换成 key=value&key=value的形式,并且拼接到url之后
  // 2、如果没有params参数,则不管
  if (params) {
    // 把对象转换成 key=value&key=value 的方法
    // 固定写法:new URLSearchParams(obj).toString()
    let str = new URLSearchParams(params).toString()
    // console.log(str)
    // 拼接到url上
    url += '?' + str
  }

  // 最终的结果
  let res
  // 判断是否有data参数,如果有,则需要设置给body,否则不需要设置
  if (data) {
    // 如果有data参数,此时直接设置
    res = await fetch(url, {
      method: method,
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(data)
    })
  } else {
    res = await fetch(url)
  }

  return res.json()
}


// 测试代码1:
// 请求方式:get
// 接口地址:http://ajax-base-api-t.itheima.net/api/getbooks
// 查询参数(可选):
//   1、id:需要查询的图书id
async function fn1() {
  let result1 = await http({
    method: 'get',
    url: 'http://ajax-base-api-t.itheima.net/api/getbooks',
    params: {
      id: 1
    }
  })
  console.log(result1)
}
fn1()

// 测试代码2:
// 请求方式:post
// 接口地址:http://ajax-base-api-t.itheima.net/api/addbook
// 请求体参数:
//  1、书名:bookname
//  2、作者:author
//  3、出版社:publisher
async function fn2() {
  let result2 = await http({
    method: 'post',
    url: 'http://ajax-base-api-t.itheima.net/api/addbook',
    data: {
      bookname: '魔法书111',
      author: '嘻嘻',
      publisher: '哈哈哈'
    }
  })
  console.log(result2)
}
fn2()

五、fetch 实战

图书管理案例

获取所有图书:
    1、接口地址:http://ajax-base-api-t.itheima.net/api/getbooks
    2、请求方式:get

添加图书:
    1、接口地址:http://ajax-base-api-t.itheima.net/api/addbook
    2、请求方式:post
    3、请求体参数:
        1、bookname:图书的名称
        2、author:作者
        3、publisher:出版社

删除图书:
    1、接口地址:http://ajax-base-api-t.itheima.net/api/delbook
    2、请求方式:delete
    3、查询参数:
        1、id:需要删除图片的id

1. 渲染功能

步骤:

  1. 把渲染的代码封装成函数。
  2. 通过封装好的 http 函数,获取所有图书数据。
  3. 遍历返回的图书数组,每遍历一项,就创建一个 tr 出来,拼接成完整字符串再一起添加到 tbody 中去。
javascript
// 把渲染的代码封装成函数
async function render() {
  // 获取数据
  let res = await http({
    method: 'get',
    url: 'http://ajax-base-api-t.itheima.net/api/getbooks',
  })
  console.log(res.data)

  let htmlStr = ''
  // 遍历数组,每遍历一项,就创建一个tr出来,拼接成完整字符串再一起添加到tbody中去
  res.data.forEach(item => {
    // console.log(item)
    htmlStr += `
      <tr>
        <th scope="row">${item.id}</th>
        <td>${item.bookname}</td>
        <td>${item.author}</td>
        <td>${item.publisher}</td>
        <td>
          <button type="button" class="btn btn-link btn-sm" data-id="${item.id}">
            删除
          </button>
        </td>
      </tr>
    `
  })
  // console.log(htmlStr)
  // 把拼接好的数据,设置到tbody中去
  tbody.innerHTML = htmlStr
}

2. 添加功能

步骤:

  1. 给 form 注册 submit 事件。
  2. 阻止浏览器默认行为(提交的默认跳转功能)。
  3. 通过 serialize 插件获取表单数据。
  4. 通过封装好的 http 函数发送数据给服务器。
  5. 判断 res 的 status 是否成功。
    1. 如果成功,此时重新渲染,并且重置表单。
    2. 如果不成功,弹框提示错误信息即可。
javascript
// 表单提交功能
form.addEventListener('submit', async function (e) {
  //  阻止浏览器默认行为
  e.preventDefault()

  // 获取表单数据 ——> 通过serialize插件获取
  let result = serialize(form, { hash: true })  // { hash: true } 将序列化后的结果从查询字符串格式转换为 JavaScript 对象(也称为哈希或字典), 用于 post 请求
  console.log(result)

  // 把获取的表单数据,发送给服务器
  let res = await http({
    method: 'post',
    url: 'http://ajax-base-api-t.itheima.net/api/addbook',
    data: result
  })
  console.log(res)

  // 通过res的status判断是否添加成功
  if (res.status === 201) {
    // 重新渲染
    render()
    // 重置表单
    form.reset()
  } else {
    // 如果不成功,弹框提示错误信息即可
    alert(res.msg)
  }

})

3. 删除数据

步骤:

  1. 利用事件委托,给 tbody 注册点击事件。
  2. 判断 e.target.tagName 是不是按钮。
  3. 如果是按钮,则通过封装好的 http 函数发送请求删除数据。
  4. 此时需要知道删除的这一项的 id,则再渲染 button 时就可以把 id 添加到 dom 结构上。
  5. 判断是否删除成功,如果删除成功,则需要重新加载。
javascript
// 删除功能 ——> 利用事件委托
tbody.addEventListener('click', async function (e) {
  //  console.log(e.target.tagName)

  // 判断如果点击的是按钮,才进行删除
  if (e.target.tagName === 'BUTTON') {
    let res = await http({
      method: 'delete',
      url: 'http://ajax-base-api-t.itheima.net/api/delbook',
      params: {
        id: e.target.dataset.id
      }
    })
    // console.log(res)
    // 判断如果删除成功,此时需要重新加载
    if (res.status === 200) {
      render()
    }
  }
})

第四章:Axios

一、介绍

1. 是什么?

Axios 是一个基于 promise 网络请求库,作用于 node.js 和浏览器中。在服务端它使用原生 node.js http 模块,而在客户端 (浏览端) 则使用 XMLHttpRequests。

2. 安装

在 Node.js 中:

bash
npm install axios
# 或者
yarn add axios

使用 CDN 方式:

html
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>

二、使用文档

1. Axios API

可以向 axios 传递相关配置来创建请求。

axios(config)

js
// 发起一个post请求
axios({
  method: 'post',
  url: '/user/12345',
  data: {
    firstName: 'Fred',
    lastName: 'Flintstone'
  }
});

js
// 在 node.js 中用GET请求获取远程图片
axios({
  method: 'get',
  url: 'http://bit.ly/2mTM3nY',
  responseType: 'stream'
})
  .then(function (response) {
    response.data.pipe(fs.createWriteStream('ada_lovelace.jpg'))
  });

axios(url[, config])

js
// 发起一个 GET 请求 (默认请求方式)
axios('/user/12345');

请求方式别名

axios.request(config)
axios.get(url[, config])
axios.delete(url[, config])
axios.head(url[, config])
axios.options(url[, config])
axios.post(url[, data[, config]])
axios.put(url[, data[, config]])
axios.patch(url[, data[, config]])
axios.postForm(url[, data[, config]])
axios.putForm(url[, data[, config]])
axios.patchForm(url[, data[, config]])

注意:在使用别名方法时, url、method、data 这些属性都不必在配置中指定。

2. 默认配置

指定的默认配置,它将作用于每个请求。

js
import axios = require('axios');

axios.defaults.baseURL = 'https://api.example.com';
axios.defaults.timeout = 1000;
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';

3. Axios 实例

可以使用自定义配置新建一个实例。

axios.create([config])

js
const instance = axios.create({
  baseURL: 'https://some-domain.com/api/',
  timeout: 1000,
  headers: {'X-Custom-Header': 'foobar'}
});

4. 请求配置

json
{
  // `url` 是用于请求的服务器 URL
  url: '/user',

  // `method` 是创建请求时使用的方法
  method: 'get', // 默认值

  // `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。
  // 它可以通过设置一个 `baseURL` 便于为 axios 实例的方法传递相对 URL
  baseURL: 'https://some-domain.com/api/',

  // 自定义请求头
  headers: {'X-Requested-With': 'XMLHttpRequest'},

  // `params` 是与请求一起发送的 URL 参数
  // 必须是一个简单对象或 URLSearchParams 对象
  // ?ID=12345
  params: {
    ID: 12345
  },

  // `data` 是作为请求体被发送的数据
  // 仅适用 'PUT', 'POST', 'DELETE 和 'PATCH' 请求方法
  // 在没有设置 `transformRequest` 时,则必须是以下类型之一:
  // - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
  // - 浏览器专属: FormData, File, Blob
  // - Node 专属: Stream, Buffer
  data: {
    firstName: 'Fred'
  },

  // `proxy` 定义了代理服务器的主机名,端口和协议。
  // 您可以使用常规的`http_proxy` 和 `https_proxy` 环境变量。
  // `proxy` 设置为 `false` 可以禁用代理功能,同时环境变量也会被忽略。
  // `auth`表示应使用HTTP Basic auth连接到代理,并且提供凭据。
  // 这将设置一个 `Proxy-Authorization` 请求头,它会覆盖 `headers` 中已存在的自定义 `Proxy-Authorization` 请求头。
  // 如果代理服务器使用 HTTPS,则必须设置 protocol 为`https`
  proxy: {
    protocol: 'https',
    host: '127.0.0.1',
    port: 9000,
    auth: {
      username: 'mikeymike',
      password: 'rapunz3l'
    }
  },
}

5. 响应结构

一个请求的响应包含以下信息。

js
{
  // `data` 由服务器提供的响应
  data: {},

  // `status` 来自服务器响应的 HTTP 状态码
  status: 200,

  // `statusText` 来自服务器响应的 HTTP 状态信息
  statusText: 'OK',

  // `headers` 是服务器响应头
  // 所有的 header 名称都是小写,而且可以使用方括号语法访问
  // 例如: `response.headers['content-type']`
  headers: {},

  // `config` 是 `axios` 请求的配置信息
  config: {},

  // `request` 是生成此响应的请求
  // 在node.js中它是最后一个ClientRequest实例 (in redirects),
  // 在浏览器中则是 XMLHttpRequest 实例
  request: {}
}

当使用 then 时,您将接收如下响应:

js
axios.get('/user/12345')
  .then(function (response) {
    console.log(response.data);
    console.log(response.status);
    console.log(response.statusText);
    console.log(response.headers);
    console.log(response.config);
  });

6. 拦截器

axios.interceptors.request.useaxios.interceptors.response.use 都接受两个参数,它们都是函数。

  • axios.interceptors.request.use 的参数:

    • 第一个参数是一个函数(成功回调),它接受一个 config 对象作为参数。这个 config 对象包含了即将被发送的请求的配置信息。你可以在这个函数中修改 config 对象,然后返回它,这样修改后的配置会被应用到请求上。

    • 第二个参数也是一个函数(失败回调),它接受一个 error 对象作为参数。这个函数会在请求发送失败时被调用。

  • axios.interceptors.response.use 的参数:

    • 第一个参数是一个函数(成功回调),它接受一个 response 对象作为参数。这个 response 对象包含了服务器的响应信息。你可以在这个函数中对 response 进行处理,然后返回处理后的结果。

    • 第二个参数也是一个函数(失败回调),它接受一个 error 对象作为参数。这个函数会在请求的响应返回错误时被调用。

举例:在请求或响应被 then 或 catch 处理前拦截它们。

js
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
  /**
   * 在发送请求之前做些什么?
   * 比如:给用户显示加载动画
   *      对原来配置进行修改,添加认证头等
   */
  return config;
}, function (error) {
  // 对请求错误做些什么
  return Promise.reject(error);
});

// 添加响应拦截器
axios.interceptors.response.use(function (response) {
  // 2xx 范围内的状态码都会触发该函数。
  // 对响应数据做点什么
  return response;
}, function (error) {
  // 超出 2xx 范围的状态码都会触发该函数。
  // 对响应错误做点什么
  return Promise.reject(error);
});

如果你稍后需要移除拦截器,可以这样:

js
const myInterceptor = axios.interceptors.request.use(function () {/*...*/});
axios.interceptors.request.eject(myInterceptor);

可以给自定义的 axios 实例添加拦截器。

js
const instance = axios.create();
instance.interceptors.request.use(function () {/*...*/});

三、使用例子

1. get 使用例子

例子:获取某个用户信息,携带用户 ID。

js
const axios = require('axios');

// 方法一
axios.get('/user?ID=12345')
  .then(function (response) {
    // 处理成功情况
    console.log(response);
  })
  .catch(function (error) {
    // 处理错误情况
    console.log(error);
  })
  .finally(function () {
    // 总是会执行
  });

// 方法二
axios.get('/user', {
  params: {
    ID: 12345
  }
})
.then(function (response) {
  console.log(response);
})
.catch(function (error) {
  console.log(error);
})
.finally(function () {
  // 总是会执行
});

// 方法三: 支持async/await用法
async function getUser() {
  try {
    const response = await axios.get('/user?ID=12345');
    console.log(response);
  } catch (error) {
    console.error(error);
  }
}

2. post使用例子

1)JSON 格式

发起一个 POST 请求。

js
// 方法一
axios.post('/user', {
  firstName: 'Fred',
  lastName: 'Flintstone'
})
.then(function (response) {
  console.log(response);
})
.catch(function (error) {
  console.log(error);
});

// 方法二
axios.post('/user', {
  data: {
    firstName: 'Fred',
    lastName: 'Flintstone'
  }
})
.then(function (response) {
  console.log(response);
})
.catch(function (error) {
  console.log(error);
});

发起多个并发请求。

js
// 1. 定义请求
function getUserAccount() {
  return axios.get('/user/12345');
}

function getUserPermissions() {
  return axios.get('/user/12345/permissions');
}

// 2. 发起多个请求
// 2.1 方法一
const [acct, perm] = await Promise.all([getUserAccount(), getUserPermissions()]);
// 2.2 方法二
Promise.all([getUserAccount(), getUserPermissions()])
  .then(function ([acct, perm]) {
    // ...
  });

将 HTML Form 转换成 JSON 进行请求。

js
const {data} = await axios.post('/user', document.querySelector('#my-form'), {
  headers: {
    'Content-Type': 'application/json'
  }
})

这样就不需要用 serialize 插件了。

2)Multipart

multipart/form-data

html
<form id="myForm">
  <label>
    Photo:
    <input type="file" id="fileInput" name="photo">
  </label>
  <button type="button" id="submitButton">Submit</button>
</form>

js
const {data} = await axios.post('https://httpbin.org/post', {
  firstName: 'Fred',
  lastName: 'Flintstone',
  orders: [1, 2, 3],
  photo: document.querySelector('#fileInput').files
}, {
  headers: {
    'Content-Type': 'multipart/form-data'
  }
})
3)URL encoded form

application/x-www-form-urlencoded

js
const {data} = await axios.post('https://httpbin.org/post', {
    firstName: 'Fred',
    lastName: 'Flintstone',
    orders: [1, 2, 3]
  }, {
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded'
    }
})

四、封装 Axios

为什么热衷于封装请求库

axios 非常好用,当我们在多个组件中需要发生网络请求的时候,多个组件都需要导入 axios 实例,再通过 axios 实例发送网络请求。

这样的用法是没有问题的,但是 axios 毕竟是一个第三方的库,不是官方维护的。如果有一天 axios 不再进行维护了,我们项目中使用 axios 的地方都需要重构,这个工程量是非常大的,修改起来也非常不方便。

那么解决方案是,我们使用 axios 封装一个自己的 request 函数,当 axios 真的不再维护,我们修改代码时, 只需要修改自己封装的函数即可,这样重构代码会非常简单。

js
import axios from "axios";

class myRequest {
  // 传入默认配置信息, 创建新的实例
  constructor(baseURL, timeout=10000) {
    this.instance = axios.create({
      baseURL,
      timeout
    })
  }

  // 1.封装request
  request(config) {
    // 返回一个Promise, 对数据转换
    return new Promise((reslove, reject) => {
      this.instance.request(config).then(res => {
        reslove(res.data)
      }).catch(err => {
        reject(err)
      })
    })
  }

  // 2.封装get
  get(config) {
    //  调用自己的request函数
    return this.request({ ...config, method: "get" })
  }

  // 3.封装psot
  post(config) {
    //  调用自己的request函数
    return this.request({ ...config, method: "post" })
  }
}

export default new myRequest()

Updated at:

Released under the MIT License.