Ajax基础知识

497 阅读6分钟

Ajax 是什么

Ajax 是 Asynchronous JavaScript and XML(异步 JavaScript 和 XML)的简写 。

  • Ajax中的异步:可以异步地向服务器发送请求,在等待响应的过程中,不会阻塞当前页面,浏览器可以做自己的事情。直到成功获取响应后,浏览器才开始处理响应数据。
  • XML(可扩展标记语言)是前后端数据通信时传输数据的一种格式。 XML 现在已经不怎么用了,比较常用的是 JSON。

Ajax 其实就是浏览器与服务器之间的一种异步通信方式。

使用 Ajax 可以在不重新加载整个页面的情况下,对页面的某部分进行更新。

Ajax 的基本用法

XMLHttpRequest

Ajax 想要实现浏览器与服务器之间的异步通信,需要依靠 XMLHttpRequest,它是一个构造函数。

注意:不论是 XMLHttpRequest,还是 Ajax,都没有和具体的某种数据格式绑定。

Ajax 的使用步骤

  1. 创建 xhr 对象

  2. 监听事件,处理响应

    当获取到响应后,会触发 xhr 对象的 readystatechange 事件,可以在该事件中对响应进行处理

    • readystatechange 事件监听 readyState 这个状态的变化

    • 它的值从 0 ~ 4,一共 5 个状态

    • 0:未初始化。尚未调用 open()

    • 1:启动。已经调用 open(),但尚未调用 send()

    • 2:发送。已经调用 send(),但尚未接收到响应

    • 3:接收。已经接收到部分响应数据

    • 4:完成。已经接收到全部响应数据,而且已经可以在浏览器中使用了

    readystatechange 事件也可以配合 addEventListener 使用,不过要注意,IE6~8 不支持 addEventListener

    为了兼容性,readystatechange 中不使用 this,而是直接使用 xhr

    由于兼容性的原因,最好放在 open() 之前

  3. 准备发送请求

    • 调用 open() 并不会真正发送请求,而只是做好发送请求前的准备工作

      • 第一个参数 HTTP 方法 GET、POST、PUT、DELETE
      • 第二个参数 地址 URL https://www.baidu.com/search/suggest?words=js ./index.html ./index.xml ./index.txt
      • 第三个参数 是否异步 true
  4. 发送请求

    • 调用 send() 正式发送请求,send() 的参数是通过请求体携带的数据

      • xhr.send(null) GET
      • xhr.send('username=alex&age=18') POST application/x-www-form-urlencoded
      • xhr.send(JSON.stringify({username:'xiaoliu'age:18})) POST application/JSON
 const url = 'https://www.imooc.com/api/http/search/suggest?words=js'
 // 创建 xhr 对象
 const xhr = new XMLHttpRequest()
 ​
 // 监听事件,处理响应
 xhr.onreadystatechange = () => {
   // 数据还没准备好,先返回,没必要进行下去
   if (xhr.readyState !== 4) return
   // HTTP CODE
   // 获取到响应后,响应的内容会自动填充 xhr 对象的属性
   // xhr.status:    HTTP           200   404
   // xhr.statusText:HTTP 状态说明   OK    Not Found
   if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
     console.log(xhr.responseText) // {"code":200,"data":[{"word":"jsp"},{"word":"js"},{"word":"json"},{"word":"js \u5165\u95e8"},{"word":"jstl"}]}
     console.log(typeof xhr.responseText) // string
   }
 }
 // //使用addEventListener监听事件
 // xhr.addEventListener('readystatechange', () => {}, false)
 ​
 // 准备发送请求
 xhr.open('GET', url, true)
 ​
 // 准备发送请求
 // 可以不加null,不过为了保证兼容性,建议加上
 xhr.send(null) // XHR finished loading: GET "https://www.imooc.com/api/http/search/suggest?words=js".

GET请求

携带数据

GET 请求不能通过请求体携带数据,但可以通过请求头携带

 const url = 'https://www.imooc.com/api/http/search/suggest?words=js&username=alex&age=18'
 const xhr = new XMLHttpRequest()
 ​
 xhr.onreadystatechange = () => {
   if (xhr.readyState != 4) return
   if ((xhr.status >= 200 && xhr < 300) || xhr.status === 304) {
     console.log(xhr.responseText)
   }
 }
 ​
 xhr.open('GET', url, true)
 ​
 xhr.send(null)
 ​
 // 不会报错,但不会发送数据
 // xhr.send('sex=male')

当发起一个GET请求时,参数会以url string的形式进行传递,即?后的字符串则为其请求参数,并以&作为分隔符

image-20210905103625193

数据编码

如果携带的数据是非英文字母的话,比如说汉字,就需要编码之后再发送给后端,不然会造成乱码问题

可以使用 encodeURIComponent() 编码

 const url = `https://www.imooc.com/api/http/search/suggest?words=${encodeURIComponent('前端')}`

image-20210904153437040

POST请求

携带数据

POST 请求主要通过请求体携带数据,同时也可以通过请求头携带

 const url = 'https://www.imooc.com/api/http/search/suggest?words=js'
 const xhr = new XMLHttpRequest()
 ​
 xhr.onreadystatechange = () => {
   if (xhr.readyState != 4) return
 ​
   if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
     console.log(xhr.responseText)
   }
 }
 ​
 xhr.open('POST', url, true)
 ​
 // 如果想发送数据,直接写在 send() 的参数位置,一般是字符串
 xhr.send('username=alex&age=18')

image-20210904160627771

数据编码

 xhr.send(`username = ${encodeURIComponent('张三')}&age=18`)

JSON

JSON 是什么

JSON 是 JavaScript Object Notation(JavaScript 对象表示法) 的简写

Ajax 发送和接收数据的一种格式

为什么需要 JSON

JSON 有 3 种形式,每种形式的写法都和 JS 中的数据类型很像,可以很轻松的和 JS 中的数据类型互相转换

JSON的三种形式

1.简单值形式

JSON 的简单值形式就对应着 JS 中的基础数据类型

数字 字符串 布尔 null

 5
 ​
 "string"
 ​
 false
 ​
 null

注意事项

① JSON 中没有 undefined 值

② JSON 中的字符串必须使用双引号

③ JSON 中是不能注释的

2.对象形式

JSON 的对象形式就对应着 JS 中的对象

 {
    "name":"小刘",
    "age":18,
     "hobby":["看书","绘画"],
     "family":{
         "father":"老刘",
         "mother":"老岳"
     }
 }

注意事项

①JSON 中对象的属性名必须用双引号,属性值如果是字符串也必须用双引号

②JSON 中只要涉及到字符串,就必须使用双引号

③不支持 undefined

3.数组形式

JSON 的数组形式就对应着 JS 中的数组

 [1, "hi", null]
 ​
 [     {         "id":1,         "username":"小刘",         "comment":"666"     },     {         "id":2,         "username":"小孙",         "comment":"999"     } ]

注意事项

①数组中的字符串必须用双引号

②JSON 中只要涉及到字符串,就必须使用双引号

③不支持 undefined

JSON的常用方法

1.JSON.parse()

JSON.parse() 可以将 JSON 格式的字符串解析成 JS 中的对应值

一定要是合法的 JSON 字符串,否则会报错

 const xhr = new XMLHttpRequest()
 ​
 xhr.onreadystatechange = () => {
   if (xhr.readyState != 4) return
 ​
   if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
     console.log(JSON.parse(xhr.responseText)) // {code: 200, data: {word: "jsp"},{word: "js"},{word: "json"},{word: "js 入门"},{word: "jstl"}}
     console.log(JSON.parse(xhr.responseText).data) // [{word: "jsp"},{word: "js"},{word: "json"},{word: "js 入门"},{word: "jstl"}]
   }
 }
 ​
 xhr.open('GET', 'https://www.imooc.com/api/http/search/suggest?words=js', true)
 ​
 xhr.send(null)

image-20210905104831699

2.JSON.stringify()

JSON.stringify() 可以将 JS 的基本数据类型、对象或者数组转换成 JSON 格式的字符串

 const xhr = new XMLHttpRequest()
 ​
 xhr.open('POST', 'https://www.imooc.com/api/http/search/suggest?words=js', true)
 xhr.send(
   JSON.stringify({
     username: '小刘',
     age: 18
   })
 )

image-20210905104946986

3.使用 JSON.parse() 和 JSON.stringify() 封装 localStorage

 //封装storage.js
 const storage = window.localStorage
 ​
 // 设置
 const set = (key, value) => {
   storage.setItem(key, JSON.stringify(value))
 }
 ​
 // 获取
 const get = key => {
   return JSON.parse(storage.getItem(key))
 }
 ​
 // 删除
 const remove = key => {
   storage.removeItem(key)
 }
 ​
 // 清空
 const clear = () => {
   storage.clear()
 }
 ​
 export { set, get, remove, clear }
 //导入html使用
 import { get, set, remove, clear } from './storage.js'
 ​
 set('username', '小刘')
 console.log(get('username')) // 小刘
 ​
 set('zs', {
   name: '张三',
   age: 18
 })
 console.log(get('zs')) // {name: "张三", age: 18}
 ​
 remove('username')
 console.log(get('username')) // null
 clear()
 console.log(get('zs')) // null

初识跨域

1.跨域是什么

向一个域发送请求,如果要请求的域和当前域是不同域,就叫跨域

不同域之间的请求,就是跨域请求

 //不同域,跨域,被浏览器阻止
 // Access to XMLHttpRequest at 'https://www.baidu.com/' from origin 'http://127.0.0.1:5500' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
 const url = 'https://www.baidu.com' 
 const xhr = new XMLHttpRequest()
 ​
 xhr.onreadystatechange = () => {
   if (xhr.readyState != 4) return
 ​
   if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
     console.log(xhr.responseText) 
   }
 }
 ​
 xhr.open('GET', url, true)
 ​
 xhr.send(null) // GET https://www.baidu.com/ net::ERR_FAILED

2.什么是不同域,什么是同域

https(协议):// www.baidu.com(域名):443(端口号)/search(路径)

协议、域名、端口号,任何一个不一样,就是不同域

与路径无关,路径一不一样无所谓

 // 不同域名
 https://www.baidu.com/search
 http://www.baidu.com/search
 ​
 http://www.baidu.com/search
 http://m.baidu.com/search
 http://baidu.com/search
 ​
 https://www.baidu.com:443/search
 https://www.baidu.com:80/search
 ​
 // 同域名
 https://www.baidu.com/
 https://www.baidu.com/search

3.跨域请求为什么会被阻止

阻止跨域请求,其实是浏览器本身的一种安全策略--同源策略

同源策略/SOP(Same origin policy)是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个IP地址,也非同源。

其他客户端或者服务器都不存在跨域被阻止的问题

4.跨域解决方案

①CORS 跨域资源共享

② JSONP

优先使用 CORS 跨域资源共享,如果浏览器不支持 CORS 的话,再使用 JSONP

CORS 跨域资源共享

1.CORS 是什么

设置通过后端完成,前端只负责沟通

Access-Control-Allow-Origin: *

表明允许所有的域名来跨域请求它,* 是通配符,没有任何限制

只允许指定域名的跨域请求

Access-Control-Allow-Origin: http://127.0.0.1:5500

2.使用 CORS 跨域的过程

① 浏览器发送请求

② 后端在响应头中添加 Access-Control-Allow-Origin 头信息

③ 浏览器接收到响应

④ 如果是同域下的请求,浏览器不会额外做什么,这次前后端通信就圆满完成了

⑤ 如果是跨域请求,浏览器会从响应头中查找是否允许跨域访问

⑥ 如果允许跨域,通信圆满完成

⑦ 如果没找到或不包含想要跨域的域名,就丢弃响应结果

3.CORS 的兼容性

IE10 及以上版本的浏览器可以正常使用 CORS

JSONP

1.JSONP 的原理

script 标签跨域不会被浏览器阻止

JSONP 主要就是利用 script 标签,加载跨域文件

2.使用 JSONP 实现跨域

服务器端准备好 JSONP 接口

https://www.imooc.com/api/http/jsonp?callback=handleResponse

动态加载 JSONP 接口

 const script = document.createElement('script')
 script.src = 'https://www.imooc.com/api/http/jsonp?callback=handleResponse'
 document.body.appendChild(script)
 ​
 const handleResponse = data => {
   console.log(data)
 }

手动加载 JSONP 接口

 <script src="https://www.imooc.com/api/http/jsonp?callback=handleResponse"></script>

\

XHR的属性

1.responseType 和 response 属性

 // 1.responseType 和 response 属性
 const url = 'https://www.imooc.com/api/http/search/suggest?words=js'
 const xhr = new XMLHttpRequest()
 ​
 xhr.onreadystatechange = () => {
   if (xhr.readyState != 4) return
 ​
   if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
     // 文本形式的响应内容
     // responseText 只能在没有设置 responseType 或者 responseType = '' 或 'text' 的时候才能使用
     console.log('responseText:', xhr.responseText) //ajax.js:11 Uncaught DOMException: Failed to read the 'responseText' property from 'XMLHttpRequest': The value is only accessible if the object's 'responseType' is '' or 'text' (was 'json').
 ​
     // 可以用来替代 responseText
     console.log('response:', xhr.response) // {code: 200, data: Array(5)},json格式字符串,自动调用json.parse转换数据类型
     // console.log(JSON.parse(xhr.responseText))
   }
 }
 xhr.open('GET', url, true)
 ​
 // xhr.responseType = ''
 // xhr.responseType = 'text'
 xhr.responseType = 'json' //设置为json格式,response会报错
 ​
 xhr.send(null)
 ​
 // IE6~9 不支持,IE10 开始支持

设置response='json'的时候使用xhr.responseText

image-20210905111009385

2.timeout 属性

设置请求的超时时间(单位 ms)

 const url = 'https://www.imooc.com/api/http/search/suggest?words=js'
 const xhr = new XMLHttpRequest()
 ​
 xhr.onreadystatechange = () => {
   if (xhr.readyState != 4) return
 ​
   if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
     console.log(xhr.response)
   }
 }
 xhr.open('GET', url, true)
 ​
 xhr.timeout = 5 // 使用极短的时间访问失败
 ​
 xhr.send(null) // XHR failed loading: GET "https://www.imooc.com/api/http/search/suggest?words=js".
 ​
 // IE6~7 不支持,IE8 开始支持
 ​

image-20210905082758644

3.withCredentials 属性

指定使用 Ajax 发送请求时是否携带 Cookie

使用 Ajax 发送请求,默认情况下,同域时,会携带 Cookie;跨域时,不会

xhr.withCredentials = true

最终能否成功跨域携带 Cookie,还要看服务器同不同意

 const url = 'https://www.imooc.com/api/http/search/suggest?words=js'
 ​
 const xhr = new XMLHttpRequest()
 ​
 xhr.onreadystatechange = () => {
   if (xhr.readyState != 4) return
 ​
   if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
     console.log(xhr.response) //Access to XMLHttpRequest at 'https://www.imooc.com/api/http/search/suggest?words=js' from origin 'http://127.0.0.1:5500' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
   }
 }
 ​
 xhr.open('GET', url, true)
 ​
 xhr.withCredentials = true
 ​
 xhr.send(null)
 ​
 // IE6~9 不支持,IE10 开始支持

服务器不同意,携带失败

image-20210905111302526

XHR的方法

1.abort()

终止当前请求

一般配合 abort 事件一起使用

 const url = 'https://www.imooc.com/api/http/search/suggest?words=js'
 ​
 const xhr = new XMLHttpRequest()
 ​
 xhr.onreadystatechange = () => {
   if (xhr.readyState != 4) return
 ​
   if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
     console.log(xhr.response)
   }
 }
 ​
 xhr.open('GET', url, true)
 ​
 xhr.send(null) // XHR failed loading: GET "https://www.imooc.com/api/http/search/suggest?words=js".
 ​
 xhr.abort()

image-20210905111347997

2.setRequestHeader()

可以设置请求头信息

xhr.setRequestHeader(头部字段的名称, 头部字段的值)

 // const url = 'https://www.imooc.com/api/http/search/suggest?words=js'
 const url = 'https://www.imooc.com/api/http/json/search/suggest?words=js'
 ​
 const xhr = new XMLHttpRequest()
 ​
 xhr.onreadystatechange = () => {
   if (xhr.readyState != 4) return
 ​
   if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
     console.log(xhr.response) // {"code":200,"data":[{"word":"jsp"},{"word":"js"},{"word":"json"},{"word":"js \u5165\u95e8"},{"word":"jstl"}]}
   }
 }
 xhr.open('POST', url, true)
 ​
 // 请求头中的 Content-Type 字段用来告诉服务器,浏览器发送的数据是什么格式的
 // xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
 xhr.setRequestHeader('Content-Type', 'application/json')
 ​
 // xhr.send(null)
 xhr.send(`username=${decodeURIComponent('小刘')}&age=18`)
 xhr.send(
   JSON.stringify({
     username: '小张' // ajax.js:21 XHR finished loading: POST "https://www.imooc.com/api/http/json/search/suggest?words=js".
   })
 // 后面的send会被忽略,这里分别测试了两种类型
 )

Content-Type设置为application/x-www-form-urlencoded时:

Request Headers image-20210905111859873

image-20210905113133485

Content-Type设置为application/json时,不改变服务器接口:

Request Headersimage-20210905112756535

image-20210905113232099

Content-Type设置为application/json时,改为JSON接口:

image-20210905113320387

\

XHR的事件

1.load 事件

响应数据可用时触发

 const url = 'https://www.imooc.com/api/http/search/suggest?words=js'
 const xhr = new XMLHttpRequest()
 ​
 xhr.onload = () => {
   //响应可用的时候触发,不需要判断readyState 为 4 了
   if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
     console.log(xhr.response) //{"code":200,"data":[{"word":"jsp"},{"word":"js"},{"word":"json"},{"word":"js \u5165\u95e8"},{"word":"jstl"}]}
   }
 }
 // xhr.addEventListener(
 //   'load',
 //   () => {
 //     if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
 //       console.log(xhr.response)
 //     }
 //   },
 //   false
 // )
 ​
 xhr.open('GET', url, true)
 ​
 xhr.send(null) // ajax.js:22 XHR finished loading: GET "https://www.imooc.com/api/http/search/suggest?words=js".
 ​
 // IE6~8 不支持 load 事件

2.error 事件

请求发生错误时触发

 // const url = 'https://www.imooc.com/api/http/search/suggest?words=js'
 const url = 'https://www.iimooc.com/api/http/search/suggest?words=js'
 const xhr = new XMLHttpRequest()
 ​
 xhr.addEventListener(
   'load',
   () => {
     if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
       console.log(xhr.response)
     }
   },
   false
 )
 xhr.addEventListener(
   'error',
   () => {
     console.log('error') //error
   },
   false
 )
 ​
 xhr.open('GET', url, true)
 ​
 xhr.send(null) //GET https://www.iimooc.com/api/http/search/suggest?words=js net::ERR_TUNNEL_CONNECTION_FAILED
 ​
 // IE10 开始支持

image-20210905113559972

3.abort 事件

调用 abort() 终止请求时触发

 const url = 'https://www.imooc.com/api/http/search/suggest?words=js'
 ​
 const xhr = new XMLHttpRequest()
 ​
 xhr.addEventListener(
   'load',
   () => {
     if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
       console.log(xhr.response)
     }
   },
   false
 )
 xhr.addEventListener(
   'abort',
   () => {
     console.log('abort') // abort
   },
   false
 )
 ​
 xhr.open('GET', url, true)
 ​
 xhr.send(null) // ajax.js:26 XHR failed loading: GET "https://www.imooc.com/api/http/search/suggest?words=js".
 ​
 xhr.abort()
 ​
 // IE10 开始支持

image-20210905113636357

4.timeout 事件

请求超时后触发

 const url = 'https://www.imooc.com/api/http/search/suggest?words=js'
 ​
 const xhr = new XMLHttpRequest()
 ​
 xhr.addEventListener(
   'load',
   () => {
     if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
       console.log(xhr.response) //
     }
   },
   false
 )
 xhr.addEventListener(
   'timeout',
   () => {
     console.log('timeout') // timeout
   },
   false
 )
 ​
 xhr.open('GET', url, true)
 ​
 xhr.timeout = 5
 ​
 xhr.send(null) // XHR finished loading: GET "https://www.imooc.com/api/http/search/suggest?words=js".
 ​
 // IE8 开始支持

如果timeout时间设置过小,会超时,XHR加载失败

image-20210905113726439

FormData

可以用于发送表单数据;亦可独立于表单,用于发送键值对数据。

IE10 及以上可以支持

1.使用 Ajax 提交表单

 const login = document.getElementById('login')
 ​
 const { username, password } = login
 const btn = document.getElementById('submit')
 const url = 'https://www.imooc.com/api/http/search/suggest?words=js'
 ​
 btn.addEventListener(
   'click',
   e => {
     // 阻止表单自动提交
     e.preventDefault()
 ​
     // 表单数据验证
     // 略...
 ​
     // 发送 Ajax 请求
     const xhr = new XMLHttpRequest()
 ​
     xhr.addEventListener(
       'load',
       () => {
         if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
           console.log(xhr.response)
         }
       },
       false
     )
 ​
     xhr.open('POST', url, true)
 ​
     // 组装数据
     const data = `username=${username.value}&password=${password.value}`
 ​
     // FormData 可用于发送表单数据
     // const data = new FormData(login)
     // data.append('age', 18)
     // data.append('sex', 'male')
     // console.log(data)
     // for (const item of data) {
     //   console.log(item)
     // }
 ​
     xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
 ​
     xhr.send(data)
 ​
     // xhr.send('username=alex&password=12345')
   },
   false
 )

image-20210905115511325

2.FormData 的基本用法

 // 通过 HTML 表单元素创建 FormData 对象
 const fd = new FormData(表单元素)
 ​
 // 通过 append() 方法添加数据
 const fd = new FormData(表单元素)
 fd.append('age', 18)
 fd.append('sex', 'male')
 // 可以通过xhr.send(FormData 数据) 发送
 xhr.send(fd)

封装AJAX(学习向)

index.html

 <!DOCTYPE html>
 <html lang="en">
   <head>
     <meta charset="UTF-8" />
     <meta http-equiv="X-UA-Compatible" content="IE=edge" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <title>封装Ajax</title>
   </head>
   <body></body>
   <script type="module">
     import { ajax, get, getJSON, post } from './ajax/index.js'
 ​
     const url = 'https://www.imooc.com/api/http/search/suggest?words=js'
 ​
     const xhr = ajax(url, {
       method: 'GET',
       params: { username: '小孙' },
       data: {
         age: 18
       },
       responseType: 'json',
 ​
       success(response) {
         console.log(response)
       },
       httpCodeError(err) {
         console.log('http code error', err)
       },
       error(xhr) {
         console.log('error', xhr)
       },
       abort(xhr) {
         console.log('abort', xhr)
       },
       timeout(xhr) {
         console.log('timeout', xhr)
       }
     })
     xhr.abort()
   </script>
 </html>

ajax.js

 // 常量
 import { HTTP_GET, CONTENT_TYPE_FORM_URLENCODED, CONTENT_TYPE_JSON } from './constants.js'
 ​
 // 工具函数
 import { serialize, addURLData, serializeJSON } from './utils.js'
 ​
 // 默认参数
 import DEFAULTS from './defaults.js'
 ​
 // Ajax类
 class Ajax {
   constructor(url, options) {
     this.url = url
     this.options = Object.assign({}, DEFAULTS, options)
 ​
     // 初始化
     this.init()
   }
 ​
   // 初始化
   init() {
     const xhr = new XMLHttpRequest()
 ​
     this.xhr = xhr
 ​
     // 绑定响应事件处理程序
     this.bindEvents()
 ​
     xhr.open(this.options.method, this.url + this.addParam(), true)
 ​
     // 设置 responseType
     this.setResponseType()
 ​
     // 设置跨域是否携带 cookie
     this.setCookie()
 ​
     // 设置超时
     this.setTimeout()
 ​
     // 发送请求
     this.sendData()
   }
 ​
   // 绑定响应事件处理程序
   bindEvents() {
     const xhr = this.xhr
 ​
     const { success, httpCodeError, error, abort, timeout } = this.options
 ​
     // load
     xhr.addEventListener(
       'load',
       () => {
         if (this.ok()) {
           success(xhr.response, xhr)
         } else {
           httpCodeError(xhr.status, xhr)
         }
       },
       false
     )
 ​
     // error
     // 当请求遇到错误时,将触发error事件
     xhr.addEventListener(
       'error',
       () => {
         error(xhr)
       },
       false
     )
 ​
     // abort
     xhr.addEventListener(
       'abort',
       () => {
         abort(xhr)
       },
       false
     )
 ​
     // timeout
     xhr.addEventListener(
       'timeout',
       () => {
         timeout(xhr)
       },
       false
     )
   }
 ​
   // 检测响应的HTTP状态码是否正常
   ok() {
     const xhr = this.xhr
     return (xhr.status >= 200 && xhr.status < 300) || xhr.status === 304
   }
 ​
   // 在地址上添加数据
   addParam() {
     const { params } = this.options
 ​
     if (!params) return ''
 ​
     return addURLData(this.url, serialize(params))
   }
 ​
   // 设置 responseType
   setResponseType() {
     this.xhr.responseType = this.options.responseType
   }
 ​
   // 设置跨域是否携带 cookie
   setCookie() {
     if (this.options.withCredentials) {
       this.xhr.withCredentials = true
     }
   }
 ​
   // 设置超时
   setTimeout() {
     const { timeoutTime } = this.options
 ​
     if (timeoutTime > 0) {
       this.xhr.timeout = timeoutTime
     }
   }
 ​
   // 发送请求
   sendData() {
     const xhr = this.xhr
 ​
     if (!this.isSendData()) {
       return xhr.send(null)
     }
 ​
     let resultData = null
     const { data } = this.options
 ​
     // 发送 FormData 格式的数据
     if (this.isFormData()) {
       resultData = data
     } else if (this.isFormURLEncodedData()) {
       // 发送 application/x-www-form-urlencoded 格式的数据
 ​
       this.setContentType(CONTENT_TYPE_FORM_URLENCODED)
       resultData = serialize(data)
     } else if (this.isJSONData()) {
       // 发送 application/json 格式的数据
 ​
       this.setContentType(CONTENT_TYPE_JSON)
       resultData = serializeJSON(data)
     } else {
       // 发送其他格式的数据
 ​
       this.setContentType()
       resultData = data
     }
 ​
     xhr.send(resultData)
   }
 ​
   // 是否需要使用 send 发送数据
   isSendData() {
     const { data, method } = this.options
 ​
     if (!data) return false
 ​
     if (method.toLowerCase() === HTTP_GET.toLowerCase()) return false
 ​
     return true
   }
 ​
   // 是否需要发送 FormData 格式的数据
   isFormData() {
     return this.options.data instanceof FormData
   }
 ​
   // 是否需要发送 application/x-www.form-urlencoded
   isFormURLEncodedData() {
     return this.options.contentType.toLowerCase().includes(CONTENT_TYPE_FORM_URLENCODED)
   }
 ​
   // 是否需要发送 application/json 格式的数据
   isJSONData() {
     return this.options.contentType.toLowerCase.includes(CONTENT_TYPE_JSON)
   }
 ​
   // 设置 Content-type
   setContentType(contentType = this.options.contentType) {
     if (!contentType) return
     this.xhr.setRequestHeader('Content-Type', contentType)
   }
 ​
   // 获取 XHR 对象
   getXHR() {
     return this.xhr
   }
 }
 ​
 export default Ajax
 ​

index.js

 import Ajax from './ajax.js'
 ​
 const ajax = (url, options) => {
   return new Ajax(url, options).getXHR()
 }
 ​
 const get = (url, options) => {
   return ajax(url, { ...options, method: 'GET' })
 }
 ​
 const getJSON = (url, options) => {
   return ajax(url, { ...options, method: 'GET', responseType: 'json' })
 }
 ​
 const post = (url, options) => {
   return ajax(url, { ...options, method: 'POST' })
 }
 export { ajax, get, getJSON, post }

defaults.js

 // 常量
 import { HTTP_GET, CONTENT_TYPE_FORM_URLENCODED } from './constants.js'
 ​
 // 默认参数
 const DEFAULTS = {
   method: HTTP_GET,
   // 请求头携带的数据
   params: null,
   //   有数据的情况
   //   params: {
   //     username: '小孙',
   //     age: 18
   //   }
 ​
   data: null,
   //   有数据的情况
   //   data: {
   //     username: '小孙',
   //     age: 18
   //   }
   //   data: FormData 数据
 ​
   contentType: CONTENT_TYPE_FORM_URLENCODED,
   responseType: '',
   timeoutTime: 0,
   withCredentials: false,
 ​
   // 方法
   success() {},
   httpCodeError() {},
   error() {},
   abort() {},
   timeout() {}
 }
 ​
 export default DEFAULTS

utils.js

 // 工具函数
 ​
 // 数据序列化成 urlencoded 格式的字符串
 const serialize = param => {
   const results = []
   for (const [key, value] of Object.entries(param)) {
     results.push(`${encodeURIComponent(key)} = ${decodeURIComponent(value)}`)
   }
 ​
   // ['username=小刘','age=18']
   return results.join('&')
 }
 ​
 // 数据序列化成 JSON 格式的字符串
 const serializeJSON = param => {
   return JSON.stringify(param)
 }
 ​
 // 给 URL 添加参数
 // www.baidu.com?search=js&
 const addURLData = (url, data) => {
   if (!data) return ''
 ​
   const mark = url.includes('?') ? '&' : '?'
 ​
   return `${mark}${data}`
 }
 ​
 export { serialize, addURLData, serializeJSON }

constants.js

 // 常量
 export const HTTP_GET = 'GET'
 export const CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded'
 export const CONTENT_TYPE_JSON = 'application/json'

使用Promise改造封装好的Ajax(学习向)

index.html

 <!DOCTYPE html>
 <html lang="en">
   <head>
     <meta charset="UTF-8" />
     <meta http-equiv="X-UA-Compatible" content="IE=edge" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <title>封装Ajax</title>
   </head>
   <body></body>
   <script type="module">
     import { ajax, get, getJSON, post } from './ajax/index.js'
 ​
     const url = 'https://www.imooc.com/api/http/search/suggest?words=js'
 ​
     // const xhr = ajax(url, {
     //   method: 'GET',
     //   params: { username: '小孙' },
     //   data: {
     //     age: 18
     //   },
     //   responseType: 'json',
 ​
     //   success(response) {
     //     console.log(response)
     //   },
     //   httpCodeError(err) {
     //     console.log('http code error', err)
     //   },
     //   error(xhr) {
     //     console.log('error', xhr)
     //   },
     //   abort(xhr) {
     //     console.log('abort', xhr)
     //   },
     //   timeout(xhr) {
     //     console.log('timeout', xhr)
     //   }
     // })
     // xhr.abort()
 ​
     const p = getJSON(url, {
       params: { username: '小刘' },
       data: { age: 18 },
       timeoutTime: 10
     })
     p.xhr.abort()
     const { ERROR_HTTP_CODE, ERROR_REQUEST, ERROR_TIMEOUT, ERROR_ABORT } = p
 ​
     p.then(response => {
       console.log(response)
     }).catch(err => {
       switch (err.type) {
         case ERROR_HTTP_CODE:
           console.log(err.text)
           break
 ​
         case ERROR_REQUEST:
           console.log(err.text)
           break
         case ERROR_TIMEOUT:
           console.log(err.text)
           break
         case ERROR_ABORT:
           console.log(err.text)
           break
       }
     })
   </script>
 </html>
 ​

index.js

 import Ajax from './ajax.js'
 ​
 import {
   ERROR_HTTP_CODE,
   ERROR_REQUEST,
   ERROR_TIMEOUT,
   ERROR_ABORT,
   ERROR_HTTP_CODE_TEXT,
   ERROR_REQUEST_TEXT,
   ERROR_TIMEOUT_TEXT,
   ERROR_ABORT_TEXT
 } from './constants.js'
 ​
 const ajax = (url, options) => {
   // return new Ajax(url, options).getXHR()
   let xhr
   const p = new Promise((resolve, reject) => {
     xhr = new Ajax(url, {
       ...options,
       ...{
         success(response) {
           resolve(response)
         },
         httpCodeError(status) {
           reject({
             type: ERROR_HTTP_CODE,
             text: `${ERROR_HTTP_CODE_TEXT}:${status}`
           })
         },
         error() {
           reject({
             type: ERROR_REQUEST,
             text: `${ERROR_REQUEST_TEXT}`
           })
         },
         abort() {
           reject({
             type: ERROR_ABORT,
             text: `${ERROR_ABORT_TEXT}`
           })
         },
         timeout() {
           reject({
             type: ERROR_TIMEOUT,
             text: `${ERROR_TIMEOUT_TEXT}`
           })
         }
       }
     }).getXHR()
   })
   p.xhr = xhr
   p.ERROR_HTTP_CODE = ERROR_HTTP_CODE
   p.ERROR_REQUEST = ERROR_REQUEST
   p.ERROR_ABORT = ERROR_ABORT
   p.ERROR_TIMEOUT = ERROR_TIMEOUT
 ​
   return p
 }
 ​
 const get = (url, options) => {
   return ajax(url, { ...options, method: 'GET' })
 }
 ​
 const getJSON = (url, options) => {
   return ajax(url, { ...options, method: 'GET', responseType: 'json' })
 }
 ​
 const post = (url, options) => {
   return ajax(url, { ...options, method: 'POST' })
 }
 export { ajax, get, getJSON, post }
 ​

constants.js

 // 常量
 export const HTTP_GET = 'GET'
 export const CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded'
 export const CONTENT_TYPE_JSON = 'application/json'
 ​
 export const ERROR_HTTP_CODE = 1
 export const ERROR_HTTP_CODE_TEXT = 'HTTP状态码异常'
 ​
 export const ERROR_REQUEST = 2
 export const ERROR_REQUEST_TEXT = '请求被阻止'
 ​
 export const ERROR_TIMEOUT = 3
 export const ERROR_TIMEOUT_TEXT = '请求超时'
 ​
 export const ERROR_ABORT = 4
 export const ERROR_ABORT_TEXT = '请求终止'

参考:

慕课的前端课程