# Ajax

前端数据请求方式

前后端分离的优势

  • 早期的网页都是通过后端渲染来完成的:服务器端渲染(SSR,server side render):
    • 客户端发出请求 -> 服务端接收请求并返回相应 HTML 文档 -> 页面刷新,客户端加载新的 HTML 文档;
  • 服务器端渲染的缺点:
    • 当用户点击页面中的某个按钮向服务器发送请求时,页面本质上只是一些数据发生了变化,而此时服务器却要将重绘的整个页面再返回给浏览器加载,这显然有悖于程序员的“DRY( Don‘t repeat yourself )”原则;
    • 而且明明只是一些数据的变化却迫使服务器要返回整个 HTML 文档,这本身也会给网络带宽带来不必要的开销。
  • 有没有办法在页面数据变动时,只向服务器请求新的数据,并且在阻止页面刷新的情况下,动态的替换页面中展示的数据呢?
    • 答案正是“AJAX”。
  • AJAX 是“Asynchronous JavaScript And XML”的缩写(异步的 JavaScript 和 XML),是一种实现 无页面刷新 获取服务器数据的技术。
    • AJAX 最吸引人的就是它的“异步”特性,也就是说它可以在不重新刷新页面的情况下与服务器通信,交换数据,或更新页面。
  • 你可以使用 AJAX 最主要的两个特性做下列事:
    • 在不重新加载页面的情况下发送请求给服务器;
    • 接受并使用从服务器发来的数据。

服务器端渲染

1674728934891

前后端分离

1674728953119

HTTP

什么是 HTTP

  • 什么是 HTTP 呢?我们来看一下维基百科的解释:
    • 超文本传输协议(英语:HyperText Transfer Protocol,缩写:HTTP)是一种用于分布式、协作式和超媒体信息系统的应用层协议;
    • HTTP 是万维网的数据通信的基础,设计 HTTP 最初的目的是为了提供一种发布和接收 HTML 页面的方法;
    • 通过 HTTP 或者 HTTPS 协议请求的资源由统一资源标识符(Uniform Resource Identifiers,URI)来标识;
  • HTTP 是一个客户端(用户)和服务端(网站)之间请求和响应的标准。
    • 通过使用网页浏览器、网络爬虫或者其它的工具,客户端发起一个 HTTP 请求到服务器上指定端口(默认端口为 80);
      • 我们称这个客户端为用户代理程序(user agent);
    • 响应的服务器上存储着一些资源,比如 HTML 文件和图像。
      • 我们称这个响应服务器为源服务器(origin server);

1674729029900

网页资源的获取

  • 我们网页中的资源通常是被放在 Web 资源服务器中,由浏览器自动发送 HTTP 请求来获取、解析、展示的。

1674729083189

  • 目前我们页面中很多数据是动态展示的:
    • 比如页面中的数据展示、搜索数据、表单验证等等,也是通过在 JavaScript 中发送 HTTP 请求获取的;

HTTP 的组成

  • 一次 HTTP 请求主要包括:请求(Request)和响应(Response)

1674729144022

HTTP 的版本

  • HTTP/0.9
    • 发布于 1991 年;
    • 只支持 GET 请求方法获取文本数据,当时主要是为了获取 HTML 页面内容;
  • HTTP/1.0
    • 发布于 1996 年;
    • 支持 POST、HEAD 等请求方法,支持请求头、响应头等,支持更多种数据类型(不再局限于文本数据) ;
    • 但是浏览器的每次请求都需要与服务器建立一个 TCP 连接,请求处理完成后立即断开 TCP 连接,每次建立连接增加了性能损耗;
  • HTTP/1.1(目前使用最广泛的版本)
    • 发布于 1997 年;
    • 增加了 PUT、DELETE 等请求方法;
    • 采用持久连接(Connection: keep-alive),多个请求可以共用同一个 TCP 连接;
  • 2015 年,HTTP/2.0
  • 2018 年,HTTP/3.0

HTTP 的请求方式

  • 在 RFC 中定义了一组请求方式,来表示要对给定资源执行的操作:
    • GET:GET 方法请求一个指定资源的表示形式,使用 GET 的请求应该只被用于获取数据。
    • HEAD:HEAD 方法请求一个与 GET 请求的响应相同的响应,但没有响应体。
      • 比如在准备下载一个文件前,先获取文件的大小,再决定是否进行下载;
    • POST:POST 方法用于将实体提交到指定的资源。
    • PUT:PUT 方法用请求有效载荷(payload)替换目标资源的所有当前表示;
    • DELETE:DELETE 方法删除指定的资源;
    • PATCH:PATCH 方法用于对资源应部分修改;
    • CONNECT:CONNECT 方法建立一个到目标资源标识的服务器的隧道,通常用在代理服务器,网页开发很少用到。
    • TRACE:TRACE 方法沿着到目标资源的路径执行一个消息环回测试。
  • 在开发中使用最多的是 GET、POST 请求;
    • 在后续的后台管理项目中,我们也会使用 PATCH、DELETE 请求;

HTTP Request Header

  • 在 request 对象的 header 中也包含很多有用的信息,客户端会默认传递过来一些信息:

1674729279756

  • content-type 是这次请求携带的数据的类型:

    • application/x-www-form-urlencoded:表示数据被编码成以 ‘&’ 分隔的键 - 值对,同时以 ‘=’ 分隔键和值
    • application/json:表示是一个 json 类型;
    • text/plain:表示是文本类型;
    • application/xml:表示是 xml 类型;
    • multipart/form-data:表示是上传文件;
  • content-length:文件的大小长度

  • keep-alive:

    • http 是基于 TCP 协议的,但是通常在进行一次请求和响应结束后会立刻中断;
    • 在 http1.0 中,如果想要继续保持连接:
      • 浏览器需要在请求头中添加 connection: keep-alive;
      • 服务器需要在响应头中添加 connection:keey-alive;
      • 当客户端再次放请求时,就会使用同一个连接,直接一方中断连接;
    • 在 http1.1 中,所有连接默认是 connection: keep-alive 的;
      • 不同的 Web 服务器会有不同的保持 keep-alive 的时间;
      • Node 中默认是 5s 中;
  • accept-encoding:告知服务器,客户端支持的文件压缩格式,比如 js 文件可以使用 gzip 编码,对应 .gz 文件;

  • accept:告知服务器,客户端可接受文件的格式类型;

  • user-agent:客户端相关的信息;

HTTP 响应状态码

1674729430423

HTTP Response Header

  • 响应的 header 中包括一些服务器给客户端的信息:

1674729486298

Ajax 简介

  • AJAX 全称为 Asynchronous Javascript And XML,就是异步的 JS 和 XML。
  • 通过 AJAX 可以在浏览器中向服务器发送异步请求, 最大的优势:页面无刷新获取数据
  • AJAX 不是新的编程语言,而是一种将现有的标准组合在一起使用的新方式。

XML 简介

  • XML : 可扩展标记语言。
  • XML 被设计用来传输和存储数据。
  • XML 和 HTML 类似,不同的是 HTML 中都是预定义标签,而 XML 中没有预定义标签,全都是自定义标签,用来表示一些数据。
1
2
3
4
5
6
<!-- 例如: 用XML表示一个学生数据 -->
<student>
<name>孙悟空</name>
<age>18</age>
<gender></gender>
</student>
1
2
// 现在已经被JSON取代了
let person = { name: '孙悟空', age: 18, gender: '男' }

Ajax 的优缺点

优点

  • 可以无需刷新页面而与服务器端进行通信。
  • 允许你根据用户事件来更新部分页面内容。

缺点

  • 没有浏览历史,不能回退;
  • 存在跨域问题;
  • SEO 搜索引擎优化不友好; 比如:爬虫爬不到,但不是绝对爬不到

Ajax 的基本使用

核心对象

  • XMLHttpRequest,AJAX 的所有操作都是通过该对象进行的。

搭建测试服务器

  1. 项目初始化:
1
npm init -y
  1. 安装 express
1
npm install express
  1. 搭建测试服务器
1
2
3
4
5
6
7
8
9
10
11
12
13
const express = require('express')

const app = express()

app.get('/test', (request, response) => {
response.send('hello_test_get!!!')
})

app.listen(8080, (err) => {
if (!err) {
console.log('服务器启动成功,8080端口监听中~~~')
}
})
  1. 启动服务器
1
node server.js
  1. nodemon 工具的使用
  • 01 安装: npm install nodemon

  • 02 用 nodemon 启动服务 : nodemon server.js

使用步骤

  1. server.js : 暴露静态资源,不用考虑跨域问题
  • 访问方式: http://127.0.0.1:8080/01_ajax小试牛刀.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 引入 express
const express = require('express')

// 创建 app 实例对象
const app = express()

// 暴露静态资源
app.use(express.static(__dirname + '/src'))

// 响应 get 请求
app.get('/test_get', (request, response) => {
response.send('hello_test_get!!!')
})

// 监听端口
app.listen(8080, (err) => {
if (!err) {
console.log('服务器启动成功,8080端口监听中~~~')
}
})
  1. 使用步骤
  • 需求: 点击按钮,发起 ajax 请求,将返回的数据放到 div 中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<body>
<h1>ajax小试牛刀</h1>
<button id="btn">点我发送请求(原生js-ajax-get)</button>
<div id="content"></div>
<script>
// 获取DOM元素
const btn = document.getElementById('btn')
const content = document.getElementById('content')
// 添加事件监听
btn.onclick = () => {
// 1. 创建 xhr 实例对象
const xhr = new XMLHttpRequest()

// 2. 指定发送请求的 method url
xhr.open('GET', 'http://127.0.0.1:8080/test_get')

// 3. 发送请求
xhr.send()

// 4. 接收响应
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status >= 200 && xhr.status < 300) {
console.log(xhr.response)
content.innerHTML = `<h3>${xhr.response}</h3>`
}
}
}
</script>
</body>

XHR 的 5 种状态

  • xhr 内部有 5 种状态,值分别为:0、1、2、3、4
    • 0: 实例出来的那一刻状态就是 0,初始状态。
    • 1:open 已经调用了,但是 send 还没有调用,此时可以修改请求头内容
    • 2:send 已经调用了,已经无法修改请求头
    • 3:已经回来一部分数据了,小的数据会在此阶段一次性接收完毕,较大的数据有待进一步接收,响应头已经回来了
    • 4:数据全部接收完毕
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 4. 接收响应
// onreadystatechange: xhr 的状态改变时调用, 每改变一次,就会执行一次回调
xhr.onreadystatechange = () => {
/* if(xhr.readyState === 1){
xhr.setRequestHeader('demo',123) //配置请求头
} */
/* if(xhr.readyState === 2){
xhr.setRequestHeader('demo',123) //配置请求头报错
} */
if (xhr.readyState === 3) {
console.log('3时接收到的数据', xhr.response)
console.log('3时接收到的响应头', xhr.getAllResponseHeaders())
}
if (xhr.readyState === 4 && xhr.status >= 200 && xhr.status < 300) {
console.log(xhr.response)
content.innerHTML = `<h3>${xhr.response}</h3>`
}
}

Ajax 请求携带参数

get 请求携带参数

携带 query 参数

  • 客户端发请求时携带 query 参数:
    • 形如:key=value&key=value 就是 query 参数的 urlencoded 编码形式
1
2
// 2. 指定发送请求的 method  url 参数
xhr.open('GET', 'http://127.0.0.1:8080/test_get?name=老六&age=18') //携带query参数
  • 服务端 server.js 响应请求时获取到请求携带的 query 参数:
    • 形如:/xx/xxx/老刘/18 就是 params 参数
1
2
3
4
5
// 响应 get 请求
app.get('/test_get', (request, response) => {
console.log(request.query) //获取query参数
response.send('hello_test_get!!!')
})

携带 params 参数

  • 客户端发请求时携带 params 参数:
1
2
// 2. 指定发送请求的 method  url 参数
xhr.open('GET', 'http://127.0.0.1:8080/test_get2/老六/18') //携带query参数
  • 服务端 server.js 响应请求时获取到请求携带的 params 参数:
1
2
3
4
5
// 响应 get 请求
app.get('/test_get2/:name/:age', (request, response) => {
console.log(request.params) //获取 params 参数
response.send('hello_test2_get!!!')
})

post 请求携带参数

携带 query 参数

  • 客户端发请求时携带 query 参数:
1
2
// 2. 指定发送请求的 method  url 参数
xhr.open('POST', 'http://127.0.0.1:8080/test_post?name=老六&age=18')
  • 服务端 server.js 响应请求时获取到请求携带的 query 参数:
1
2
3
4
5
// 响应 post 请求
app.post('/test_post', (request, response) => {
console.log(request.query) //获取 query 参数
response.send('hello_test_post')
})

携带 params 参数

  • 客户端发请求时携带 params 参数:
1
2
// 2. 指定发送请求的 method  url 参数
xhr.open('POST', 'http://127.0.0.1:8080/test_post/老六/18')
  • 服务端 server.js 响应请求时获取到请求携带的 params 参数:
1
2
3
4
5
// 响应 post 请求
app.post('/test_post/:name/:age', (request, response) => {
console.log(request.params) //获取 params 参数
response.send('hello_test_post')
})

携带请求体参数(1)

  • 客户端发请求时携带 urlencoded 形式的 请求体 参数:
1
2
3
4
5
6
7
8
// 2. 指定发送请求的 method  url 参数
xhr.open('POST', 'http://127.0.0.1:8080/test_post')

// 追加响应头用于标识携带请求体参数的编码形式-urlencoded
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded')

// 3. 发送请求
xhr.send('name=老六&age=18') // post 请求携带 urlencoded 编码形式的请求体参数
  • 服务端 server.js 响应请求时获取到请求携带的 urlencoded 形式的 请求体 参数:
1
2
3
4
5
6
7
8
// 使用中间件解析 urlencoded 编码形式的请求体参数
app.use(express.urlencoded({ extended: true }))

// 响应 post 请求
app.post('/test_post', (request, response) => {
console.log(request.body) //获取 body 参数
response.send('hello_test_post')
})

携带请求体参数(2)

  • 客户端发请求时携带 json 形式的 请求体 参数:
1
2
3
4
5
6
7
8
9
// 2. 指定发送请求的 method  url 参数
xhr.open('POST', 'http://127.0.0.1:8080/test_post')

// 追加响应头用于标识携带请求体参数的编码形式-json
xhr.setRequestHeader('Content-type', 'application/json')

// 3. 发送请求
const person = { name: '老六', age: 18 }
xhr.send(JSON.stringify(person)) // post 请求携带 json 请求体参数,类型必须是字符串类型
  • 服务端 server.js 响应请求时获取到请求携带的 json 形式的 请求体 参数:
1
2
3
4
5
6
7
8
// 使用中间件解析 json 编码形式的请求体参数
app.use(express.json())

// 响应 post 请求
app.post('/test_post', (request, response) => {
console.log(request.body) //获取 body 参数
response.send('hello_test_post')
})

Ajax 解析 json 数据

    1. 客户端发请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 获取DOM元素
const btn = document.getElementById('btn')
const content = document.getElementById('content')
// 添加事件监听
btn.onclick = () => {
// 1. 创建xhr 实例对象
const xhr = new XMLHttpRequest()

// 2. 指定发送请求的 method url 参数
xhr.open('get', 'http://127.0.0.1:8080/get_person')

// responseType 用于指定返回数据的格式,获取到的响应就是从后台返回的字符串形式的json经过转换后的json数据
xhr.responseType = 'json'

// 3. 发送请求
xhr.send()

// 4. 接收响应
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status >= 200 && xhr.status < 300) {
const result = xhr.response
console.log(result)
}
}
}
    1. 服务端 server.js 响应请求,返回一个 json 形式的数据
1
2
3
4
5
6
// 响应 get 请求
app.get('/get_person', (request, response) => {
console.log('有人请求了 get_person')
const person = { name: '老王', age: 20, sex: 'nv' }
response.send(JSON.stringify(person)) // 将 json 数据转为字符串形式
})

请求异常与超时处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 获取DOM元素
const btn = document.getElementById('btn')
const content = document.getElementById('content')
// 添加事件监听
btn.onclick = () => {
// 1. 创建xhr 实例对象
const xhr = new XMLHttpRequest()

// 2. 指定发送请求的 method url 参数
xhr.open('get', 'http://127.0.0.1:8080/get_person')

// responseType 用于指定返回数据的格式
xhr.responseType = 'json'

// 3. 发送请求
xhr.send()

// 请求异常时调用
xhr.onerror = () => {
alert('当前网络不稳定,请稍后再试~~')
}

// 超时时间,比如服务器端加个定时器,5s后才返回数据
xhr.timeout = 2000
// 超时回调
xhr.ontimeout = () => {
alert('网速不给力啊,请切换网络再次尝试~')
}

// 4. 接收响应
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status >= 200 && xhr.status < 300) {
const result = xhr.response
console.log(result)
}
}
}

取消请求

    1. 点击按钮取消请求:
1
2
3
4
// 取消请求 需要将前面的按钮的 xhr 定义为一个全局变量
btn2.onclick = () => {
xhr.abort() //取消请求
}
    1. 或者可以直接在 发送请求 后面直接 取消请求,
    • 此时的状态是:
      • 如果来得及取消请求,那么请求就到不了服务器
      • 如果来不及取消请求,那么请求可以到达服务器,服务器作出响应,但是浏览器会拦截响应,使得响应回不到浏览器
1
2
3
4
// 3. 发送请求
xhr.send()
//取消请求
xhr.abort()

避免多次重复请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
let xhr
let isLoading // 用于判断是否正在发送请求
// 添加事件监听
btn.onclick = () => {
if (isLoading) xhr.abort() //取消上一次请求

// 1. 创建xhr 实例对象
xhr = new XMLHttpRequest()

// 4. 接收响应
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && (xhr.status >= 200 && xhr.status < 300)) {
isLoading = false //响应全部回来时改为false
const result = xhr.response
console.log(result);
}
}

// 2. 指定发送请求的 method url 参数
xhr.open("get", "http://127.0.0.1:8080/get_person");

// responseType 用于指定返回数据的格式
xhr.responseType = "json";

// 3. 发送请求
xhr.send()
isLoading = true // 数据还没有完全回来时,改为true,用于控制取消请求

jQuery 封装的 ajax

jquery 发送 get 请求

  • 完整写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const btn1 = $('#btn1')

btn1.click(() => {
//使用jquery发送ajax请求,完整写法
$.ajax({
url: 'http://127.0.0.1:8080/test_jquery_get', //请求地址
method: 'GET', //请求方法 不写默认是get请求
data: { school: 'atguigu' }, //携带的 query 参数
dataType: 'json', //配置响应数据格式
timeout: 2000, // 超时时间
success: (result, responseText, xhr) => {
//成功时的回调
console.log(result, responseText, xhr)
},
error: (xhr) => {
//失败和超时都会执行该回调
console.log(xhr, '请求出错了~')
},
})
})
  • 精简写法
1
2
3
4
5
6
7
8
9
10
11
12
btn1.click(() => {
//使用jquery发送ajax请求,精简写法
$.get(
'http://127.0.0.1:8080/test_jquery_get',
{ school: 'atguigu' },
(result, responseText, xhr) => {
//成功时的回调
console.log(result, responseText, xhr)
},
'json'
)
})
  • 服务端 server.js 响应请求
1
2
3
4
5
6
// 响应 get 请求
app.get('/test_jquery_get', (request, response) => {
console.log('有人请求了 test_jquery_get', request.query)
const person = { name: '老王', age: 20, sex: 'nv' }
response.send(JSON.stringify(person))
})

jquery 发送 post 请求

  • 完整写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
btn2.click(() => {
//使用jquery发送ajax请求,完整写法
$.ajax({
url: 'http://127.0.0.1:8080/test_jquery_post', //请求地址
method: 'POST', //请求方法
data: { school: 'atguigu' }, //携带的 query 参数
dataType: 'json', //配置响应数据格式
timeout: 2000, // 超时时间
success: (result, responseText, xhr) => {
//成功时的回调
console.log(result, responseText, xhr)
},
error: (xhr) => {
//失败和超时都会执行该回调
console.log(xhr, '请求出错了~')
},
})
})
  • 精简写法
1
2
3
4
5
6
7
8
9
10
11
12
btn2.click(() => {
//使用jquery发送ajax请求,精简写法
$.post(
'http://127.0.0.1:8080/test_jquery_post',
{ school: 'atguigu' },
(result, responseText, xhr) => {
//成功时的回调
console.log(result, responseText, xhr)
},
'json'
)
})
  • 服务端 server.js 响应请求
1
2
3
4
5
6
7
8
9
// 使用中间件解析 urlencoded 编码形式的请求体参数
app.use(express.urlencoded({ extended: true }))

// 响应 post 请求
app.post('/test_jquery_post', (request, response) => {
console.log('有人请求了 test_jquery_post', request.body) // 获取请求中携带的参数
const person = { name: '老王', age: 20, sex: 'nv' }
response.send(JSON.stringify(person))
})

跨域和同源策略

为什么存在跨域问题

  • 原因是浏览器为了安全,而采用的同源策略( Same origin policy )
  • 跨越的产生: 存在跨越的情况下,浏览器的 ajax 引擎会对服务器返回的数据进行拦截

什么是同源策略

  • 01 同源策略是由 Netscape 提出的一个著名的安全策略,现在所有支持 JavaScript 的浏览器都会使用这个策略

  • 02 web 是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现

  • 03 所谓同源是指 : 协议 , 域名(IP),端口 必须要完全相同

    • 即:协议、域名(IP)、端口都相同,才能算是在同一个域里,只要有一个或多个不同,就算跨域。
  • 规则举例如下:(假设已有网站地址为: http://study.cn)

1657162721849

非同源受到的限制

  • 01 Cookie 不能读取;
  • 02 DOM 无法获得;
  • 03 Ajax 请求能发送,但是浏览器不能获取到返回的数据

JSONP 解决跨域

JSONP 是什么

  • JSONP (JSON with Padding),是一个非官方的跨域解决方案,纯粹凭借程序员的聪明才智开发出来,只支持 get 请求

JSONP 的实现原理

  • 在网页有一些标签天生具有跨域能力,比如: img link iframe script 等标签发起的请求不受同源策略的限制。

  • JSONP 就是利用了 script 标签发送请求不受同源策略的限制的特点。

  • form xhr 浏览器地址栏 img script 标签,都是发起的请求都是 get 请求

  • 利用 JSONP 绕开 xhr , 借助 script 标签发起请求,避免同源策略的限制

JSONP 解决跨域的过程

    1. 客户端点击按钮时发起请求,传递一个回调名传递给服务器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<body>
<button id="btn">按钮</button>
<script>
const btn = document.getElementById('btn')
btn.onclick = () => {
// 1. 创建节点
const scriptNode = document.createElement('script')
// 2. 给节点指定 src 属性(请求地址)
scriptNode.src = 'http://localhost:8080/test_jsonp?callback=demo' // 传递一个回调名传递给服务器
// 3. 将节点放入页面
document.body.appendChild(scriptNode)

// 4. 调用方法,获取到服务器返回的数据
window.demo = (a) => {
console.log(a)
}

// 5. 移除已经使用过的script节点
document.body.removeChild(scriptNode)
}
</script>
</body>
    1. 服务端 **server.js ** 作出响应,将传递来的回调,携带数据,返回给客户端
1
2
3
4
5
6
7
// 响应 get 请求
app.get('/test_jsonp', (request, response) => {
console.log('有人请求了 test_jsonp')
const { callback } = request.query // 从请求参数中解构出 callback 变量
const person = { name: '老王', age: 20, sex: 'nv' }
response.send(`${callback}(${JSON.stringify(person)})`) // 将要返回给客户端的数据作为函数的形参传递给函数,并整体返回给客户端
})

jQuery 封装的 JSONP

    1. 客户端点击按钮时,调用 getJSON 方法, 发起请求
1
2
3
4
5
6
7
8
9
10
11
<body>
<button id="btn">按钮</button>
<script>
const btn = $('#btn')
btn.click(() => {
$.getJSON('http://localhost:8080/test_jsonp?callback=?', {}, (data) => {
console.log(data)
})
})
</script>
</body>
    1. 服务端 server.js 作出响应,将传递来的回调,携带数据,返回给客户端
1
2
3
4
5
6
7
// 响应 get 请求
app.get('/test_jsonp', (request, response) => {
console.log('有人请求了 test_jsonp')
const { callback } = request.query // 从请求参数中解构出 callback 变量
const person = { name: '老王', age: 20, sex: 'nv' }
response.send(`${callback}(${JSON.stringify(person)})`)
})

CORS 解决跨域

CORS 是什么

  • CORS (Cross-Origin Resource Sharing),跨域资源共享CORS 是官方的跨域解决方案,它的特点是不需要在客户端做任何特殊的操作,完全在服务器中进行处理,支持常见的所有请求

CORS 解决跨域

    1. 客户端仅发起请求,跨域问题在服务端解决
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<body>
<button id="btn">按钮</button>
<script>
// 获取DOM元素
const btn = document.getElementById('btn')

// 添加事件监听
btn.onclick = () => {
// 1. 创建xhr 实例对象
const xhr = new XMLHttpRequest()

// 2. 指定发送请求的 method url 参数
xhr.open('get', 'http://127.0.0.1:8080/test_get')

// 3. 发送请求
xhr.send()

// 4. 接收响应
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status >= 200 && xhr.status < 300) {
const result = xhr.response
console.log(result)
console.log(xhr.getAllResponseHeaders()) // 获取到响应头
}
}
}
</script>
</body>
    1. 服务端 server.js 作出响应,并设置响应头解决跨越
1
2
3
4
5
6
7
// 响应 get 请求  query参数
app.get('/test_get', (request, response) => {
console.log(request.query) // 获取query参数
response.setHeader('Access-Control-Allow-Origin', 'http://127.0.0.1:5500') //参数二写成 * 号,就是允许所有网站访问
response.setHeader('Access-Control-Expose-Headers', '*') // 暴露所有响应头
response.send('hello_test_get!!!')
})

PUT 请求的跨域问题解决

    1. 客户端仅发起请求,跨域问题在服务端解决
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<body>
<button id="btn">按钮</button>
<script>
// 获取DOM元素
const btn = document.getElementById('btn')

// 添加事件监听
btn.onclick = () => {
// 1. 创建xhr 实例对象
const xhr = new XMLHttpRequest()

// 2. 指定发送请求的 method url 参数
xhr.open('put', 'http://127.0.0.1:8080/test_put')

// 3. 发送请求
xhr.send()

// 4. 接收响应
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status >= 200 && xhr.status < 300) {
const result = xhr.response
console.log(result)
console.log(xhr.getAllResponseHeaders()) // 获取到响应头
}
}
}
</script>
</body>
    1. 服务端 server.js 作出响应,并设置响应头解决跨越,以及作出预请求的响应
    • get / post 请求也叫简单请求
    • put / delete 请求也叫复杂请求,在真正发起请求之前,会发起一个嗅探请求,也叫预请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 响应 put 请求,并解决put请求的跨域
// app.options 用于响应 put/delete 请求的嗅探请求,嗅探请求也叫预请求
app.options('/test_put', (request, response) => {
response.setHeader('Access-Control-Allow-Origin', '*')
response.setHeader('Access-Control-Expose-Headers', '*')
response.setHeader('Access-Control-Allow-Methods', '*')
response.send()
})

app.put('/test_put', (request, response) => {
response.setHeader('Access-Control-Allow-Origin', '*')
response.setHeader('Access-Control-Expose-Headers', '*')
response.send('hello_test_put')
})

利用中间件解决跨域

    1. 安装 cors 中间件
1
npm install cors
    1. 服务端 server.js 使用中间件即可,无需其他配置
1
2
3
4
5
// 1. 导入中间件
const cors = require('cors')

// 2. 使用中间件
app.use(cors())

扩展

连续解构赋值

1
2
3
4
5
6
7
8
9
// 连续解构赋值和重命名
let obj = { a: 1, b: { c: 2 } }
const {
b: { c },
} = obj //连续解构赋值
const {
b: { c: value },
} = obj //连续解构赋值和重命名
console.log(value) // 2

浏览器的协商缓存

  • 01 发起一次请求后,获得服务器返回的数据,返回的状态码为 200
  • 02 当在 url 没变的情况下,再次发起请求,服务器接收到了请求,但是服务器数据也没变,此时返回的状态码为 304
  • 03 此时浏览器就会走缓存,去拿上一次请求得来的数据
    • (协商缓存大概过程:浏览器去问服务器,数据变了没有,服务器说数据没变,就返回 304 状态码,浏览器就去自己缓存里拿上一次的数据)
      • 如果服务器的数据变了,就返回新的数据,响应状态码为 200
    • 浏览器和服务器协商
  • 除了 get / post / head 请求外,其他都算是复杂请求,复杂请求都会有一个预请求,预请求成功,才会发起真正的请求

IE 的 get 请求缓存问题

  • IE 浏览器的强缓存机制:
    • 发起一次请求后,获得服务器返回的数据, 返回的状态码为 200
    • 当在 url 没变的情况下,再次发起请求,IE 浏览器会直接走缓存,服务器也收不到请求,即使服务器数据变了
      • IE 会直接走缓存,不会再与服务器协商
  • 解决方法:
    • 可以在请求 url 后面携带参数, 比如:携带当前时间戳
1
xhr.open('GET', 'http://127.0.0.1:8080/get_person?t=' + Date.now())
  • IE 浏览器下的 post 请求不存在该问题