Ajax day03--XMLHttpRequest,跨域

626 阅读4分钟

一、XMLHttpRequest

01-什么是 XMLHttpRequest

// 是浏览器内置的一个构造函数
// 作用:基于 new 出来的 XMLHttpRequest 实例对象,可以发起 Ajax 的请求。
// axios 中的 axios.get()、axios.post()、axios() 方法,都是基于 XMLHttpRequest(简称:XHR) 封装出来的!
// 我们完全可以能否不用 axios 封装的 Ajax 函数,直接基于 XMLHttpRequest 发起 Ajax 请求

02-使用 XMLHttpRequest 发起 GET 请求

  • 主要的 4 个实现步骤:
    • 1.创建 xhr 对象
    • 2.调用 xhr.open() 函数
    • 3.调用 xhr.send() 函数
    • 4.监听 load 事件

image.png

03-使用XMLHttpRequest发起无参请求

<body>
<button>点我啊</button>
    <script>
      let btn = document.querySelector('button')
      btn.addEventListener('click', function() {
        // 1.创建一个异步对象
        let xhr = new XMLHttpRequest()
        // 2.发起请求:设置正确的请求报文
        // 2.1 报文行:设置请求方式和请求url,调用open方法可以设置请求行
        xhr.open('get', 'http://www.itcbc.com:3006/api/getbooks')
        // 2.2 报文头:get方式不用设置报文头,因为它的参数是进行url拼接,所以不用设置编码格式
        // 2.3 报文体:设置本次请求传递给服务器的参数,同时发起请求,get方式的参数在url中拼接,所以不用在这个位置传递参数,调用send可以发起请求
        xhr.send()

        // 3.接收响应
        // 由于是一个异步请求,所以我们不知道什么时候响应,那么当数据响应回客户端可以使用的时候,就会触发load事件,返回值就在xhr的response属性中
        xhr.addEventListener('load', function() {
          console.log(xhr.response, typeof xhr.response)
          console.log(JSON.parse(xhr.response), typeof JSON.parse(xhr.response))
        })
      })
    </script>
</body>

04-使用XMLHttpRequest发起带参请求

// 2.发起请求:设置正确的请求报文
// 2.1 报文行:设置请求方式和请求url,调用open方法可以设置请求行
// 如果有参数,在原生代码中,需要在url后面拼接
xhr.open(
  'get',
  'http://www.itcbc.com:3006/api/getbooks?bookname=' + value
)

05-使用XMLHttpRequest发起post请求

  • 提交请求体数据,需指定Content-Type头
  • 当需要提交请求体数据时,需要在 xhr.open() 之后,调用 xhr.setRequestHeader() 函数,指定请求体的编码格式

HTML解构

<form class="card-body bg-light" id="addForm">
  <!-- 书名 -->
  <div class="input-group mb-3">
    <div class="input-group-prepend">
      <span class="input-group-text">书名</span>
    </div>
    <input
      type="text"
      class="form-control"
      placeholder="请输入图书名称"
      name="bookname"
    />
  </div>
  <!-- 作者 -->
  <div class="input-group mb-3">
    <div class="input-group-prepend">
      <span class="input-group-text">作者</span>
    </div>
    <input
      type="text"
      class="form-control"
      placeholder="请输入作者名字"
      name="author"
    />
  </div>
  <!-- 出版社 -->
  <div class="input-group mb-3">
    <div class="input-group-prepend">
      <span class="input-group-text">出版社</span>
    </div>
    <input
      type="text"
      class="form-control"
      placeholder="请输入出版社名称"
      name="publisher"
    />
  </div>
  <input type="button" value="添加" class="btn btn-dark btnadd" />
</form>

JS代码

<script>
  let btnadd = document.querySelector('.btnadd')
  // 书名
  let bookname = document.querySelector('[name="bookname"]')
  // 作者
  let author = document.querySelector('[name="author"]')
  // 出版商
  let publisher = document.querySelector('[name="publisher"]')

  btnadd.addEventListener('click', function() {
    // 1.创建一个异步对象
    let xhr = new XMLHttpRequest()
    // 2.发起请求
    // 2.1 设置请求行:open(方式,地址)
    xhr.open('post', 'http://www.itcbc.com:3006/api/addbook')
    // 2.2 设置请求头:post方式需要设置请求头,默认情况下编码格式应该设置为:application/x-www-form-urlencoded
    // 通过 setRequestHeader 方法可以设置请求头
    // Content-Type:用来设置参数的编码格式的
    xhr.setRequestHeader(
      'Content-Type',
      'application/x-www-form-urlencoded'
    )
    // 2.3 设置请求体,通过send方法可以设置
    // post方式的参数需要在请求体中传递,默认的格式为:key=value&key=value
    xhr.send(
      `bookname=${bookname.value}&author=${author.value}&publisher=${publisher.value}`
    )

    // 3.接收响应
    xhr.addEventListener('load', function() {
      console.log(xhr.response)
    })
  })
</script>

06-请求体格式 和 对应的Content-Type值

  • 为了方便服务器接收数据,当提交请求体时,需要指定一个叫做Content-Type的请求头

image.png

btnadd.addEventListener('click', function() {
    // 1.创建一个异步对象
    let xhr = new XMLHttpRequest()
    // 2.发起请求
    // 2.1 设置请求行:open(方式,地址)
    xhr.open('post', `http://www.itcbc.com:3006/api/addbook`)
    // 2.2 设置请求头:post方式需要设置请求头,默认情况下编码格式应该设置为:application/x-www-form-urlencoded
    // 通过 setRequestHeader 方法可以设置请求头
    // Content-Type:用来设置参数的编码格式的
    // xhr.setRequestHeader(
    //   'Content-Type',
    //   'application/x-www-form-urlencoded'
    // )
    // 2.3 设置请求体,通过send方法可以设置
    // post方式的参数需要在请求体中传递,默认的格式为:key=value&key=value
    // xhr.send(
    //   `bookname=${bookname.value}&author=${author.value}&publisher=${publisher.value}`
    // )

    xhr.setRequestHeader('Content-Type', 'application/json')
    xhr.send(
      JSON.stringify({
        bookname: bookname.value,
        author: author.value,
        publisher: publisher.value
      })
    )

    // 3.接收响应
    xhr.addEventListener('load', function() {
      console.log(xhr.response)
    })
  })

二、数据交换格式

01-什么是数据交换格式

  • 数据交换格式,就是服务器端与客户端之间数据传输的格式。
  • 两种数据交换格式:XML(很少用) JSON(主流) image.png

02-什么是 JSON

JSON(全称:JavaScript Object Notation)是一种数据交换格式,它本质上是用字符串的方式来表示对象或数组类型的数据。例如:

image.png

03-JSON 数据

  • 用字符串的方式来表示的对象或数组类型的数据,叫做 JSON 数据。
  • JSON 数据的格式有两种:1.对象格式 2.数组格式

04-JSON 的语法要求

使用 JSON 定义 JSON 格式的数据时,要遵守以下的 6 条规则:
1. 属性名必须使用双引号包裹
2. 字符串类型的值必须使用双引号包裹
3. JSON 中不允许使用单引号表示字符串
4. JSON 中不能写注释
5. JSON 的最外层必须是对象或数组格式(其他类型也可以,但多数是对象或数组格式)
6. 不能使用 undefined 或函数作为 JSON 的值

05-对象格式的 JSON 数据

对象格式的 JSON 数据,最外层使用 {  } 进行包裹,内部的数据为 "key": "value" 的键值对结构。其中:
1. key 必须使用英文的双引号进行包裹
2. value 的值只能是字符串、数字、布尔值、null、数组、对象类型(可选类型只有这 6 种)

image.png

06-数组格式的 JSON 数据

数组格式的 JSON 数据,最外层使用 [  ] 进行包裹,内部的每一项数据之间使用英文的 , 分隔。其中:
每一项的值类型只能是字符串、数字、布尔值、null、数组、对象这 6 种类型之一。

image.png

07-把 JSON 数据转换为 JS 数据

调用浏览器内置的 JSON.parse() 函数,可以把 JSON 格式的字符串转换为 JS 数据,例如:

image.png

08-把 JS 数据转换为 JSON 数据

调用浏览器内置的 JSON.stringify() 函数,可以把 JS 数据转换为 JSON 格式的字符串,例如:

image.png

09-序列化和反序列化

  1. 把真实数据转换为字符串的过程,叫做序列化
  2. 把字符串转换为真实数据的过程,叫做反序列化

image.png

10-把 XMLHttpRequest 请求到的 JSON 数据反序列化为 JS 对象

在 xhr 对象的 load 事件中,通过 xhr.response 访问到的是 JSON 格式的字符串数据。可以调用 JSON.parse() 函数将 xhr.response 转化为 JS 对象。示例代码如下:

image.png

三、封装自己的 Ajax 函数

定义 itheima 函数的参数选项

函数是自定义的 Ajax 函数,它接收一个配置对象作为参数。配置对象中包含如下 5 个参数选项:

参数选项说明
method请求的类型(GET 或 POST)
url请求的 URL 地址
paramsURL 末尾拼接的查询参数
data请求体数据,有三种格式,分别是(FormData 格式、JSON 格式、普通字符串格式)
success请求成功之后的回调函数

实现步骤

1.简化参数及默认值处理

// 0.参数简化和处理
  let method = option.method || 'get'
  let url = option.url
  if (!url) {
    alert('一定要设置url')
    return
  }
  let params = option.params
  let data = option.data

2.创建一个异步对象

let xhr = new XMLHttpRequest()

3.处理get方式的params参数

  • 是get请求,同时传递了params
  • 将params对象转换为key=value的格式,拼接在url后面
if (params && method.toLowerCase() == 'get') {
    url = url + '?' + exchange(params)
  }

4.发起请求

image.png

5.接收响应

  • 以回调函数的参数将后台响应数据返回
// 3.接收响应
  xhr.addEventListener('load', function() {
    // 通过回调函数的参数将后台响应数据返回
    success(JSON.parse(xhr.response))
  })

整体代码结构

HTML结构

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
    <script src="./js/myajax.js"></script>
  </head>
  <body>
    <input type="text" />
    <button class="all">查询所有数据</button>
    <button class="search">查询指定名称的数据</button>
    <script>
      let input = document.querySelector('input')
      let all = document.querySelector('.all')
      let search = document.querySelector('.search')

      all.addEventListener('click', function() {
        // axios({
        //   method: 'get',
        //   url: 'http://www.itcbc.com:3006/api/getbooks',
        //   params: { bookname: '11' }
        //   // data:`bookname=111`
        // })
        axios({
          method: 'post',
          url: 'http://www.itcbc.com:3006/api/addbook',
          // data: { bookname: '11', author: 'aaaa', publisher: 'adsfdsf' }
          // data:`bookname=111`
          data: `bookname=aaaa&author=author&publisher=publisher`,
          success: function(res) {
            console.log(res)
          }
        })
      })
    </script>
  </body>
</html>

JS代码

function exchange(obj) {
  let arr = []
  for (let key in obj) {
    console.log(key, obj[key])
    arr.push(`${key}=${obj[key]}`)
  }
  return arr.join('&')
}

// 添加一个工具函数
// option:是一个配置对象
// method:请求方式
// url:请求地址
// params:是get请求方式的参数
// data:是post请求方式的参数
function axios(option) {
  // 0.参数简化和处理
  let method = option.method || 'get'
  let url = option.url
  if (!url) {
    alert('一定要设置url')
    return
  }
  let params = option.params
  let data = option.data
  let success = option.success

  // 1.创建异步对象
  let xhr = new XMLHttpRequest()
  // 2.发请求
  // 2.1 设置请求行
  // 判断:传递了params,且请求方式是get,我们需要将params对象转成字符串拼接到url后面
  if (params && method.toLowerCase() == 'get') {
    url = url + '?' + exchange(params)
  }
  xhr.open(method, url)
  // 2.2 设置请求头:只有post方式才需要进行请求头的设置
  if (method.toLowerCase() == 'post') {
    // 说明传递了key=value&key=value
    if (typeof data == 'string') {
      xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
      xhr.send(data)
    } else if (data instanceof FormData) {
      // 传递了formdata,浏览器进行设置好请求头
      xhr.send(data)
    } else {
      xhr.setRequestHeader('Content-Type', 'application/json')
      // 将对象转换为字符串
      xhr.send(JSON.stringify(data))
    }
  } else {
    xhr.send()
  }

  // 3.接收响应
  xhr.addEventListener('load', function() {
    // 通过回调函数的参数将后台响应数据返回
    success(JSON.parse(xhr.response))
  })
}

同源策略 & 跨域(面试服务)

  • 3.1 同源跨域是浏览器的安全策略

    • 可以正常的向服务器发起数据请求
    • 服务器也会正常的响应
    • 但是浏览器会阻止数据的返回
  • 3.2 同源:协议,域名(IP),端口都一样,则同源,但凡有一个不一样则跨域

    • 浏览器认为与不同源的url交互不安全
  • 3.3 解决跨域的三种常见方案

    • cors跨域:服务器端跨域

      • 在服务器端进行设置的,前端不用任何的改变
      app.use('/', (req, res, next) => {
        // * 允许所有源来发起跨域请求
        res.setHeader('Access-Control-Allow-Origin', '*')
        res.setHeader('Access-Control-Allow-Credential', 'true')
        res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS')
        next()
      })
      复制代码
      
    • jsonp跨域:浏览器跨域

      • 本质是利用了Scirpt标签的src天然跨域特性
      • 通过Script获取的内容会默认以js语法来解析
      • 在发起请求的时候,传递一个函数名称,服务器返回函数调用形式并拼接相应的数据
      • jsonp与异步对象没有任何的关系,它不是一个异步请求,只能发get请求

4、防抖

  • 4.1 是指多次触发,只执行最后一次
  • 4.2 将业务处理代码放到延迟器中
  • 4.3 每次触发事件之后,先清除之前的延迟器,再重新添加延迟器,实现重新计时
  • 4.4 代码演示:
let tid = null
 // input:内容改变就会触发
 input.addEventListener('input', function() {
    // 先清除之前所添加的延迟器
    clearTimeout(tid)
    tid = setTimeout(() => {
      axios({
        url: 'http://www.itcbc.com:3006/api/getbooks'
      }).then(res => {
        console.log(res)
      })
    }, 2000)
 })
复制代码

5、节流

  • 5.1 是指多次触发,只执行第一次
  • 5.2 定义一个标识,如果满足条件才进行处理
  • 5.3 在第一次操作之后,将标识进行重置,例如重置为false,再进行操作不再处理
  • 5.4 本次操作完成了,将标识再重置为true
  • 5.5 代码演示:
let flag = true // true表示 本次单击应该进行处理
button.addEventListener('click', function() {
    if(!flag){ return}
    flag = false
    setTimeout(() => {
      console.log(123)
      flag = true
    }, 2000)
})