Ajax学习笔记

学习来源:尚硅谷

笔记参考:努力学习的汪

0 补充知识

0.1 回调函数Callback

0.1.1 概述

首先要明白两个重点:

  1. 函数可以作为一个参数在另一个函数中被调用。

  2. JS是异步编程语言(但js是单线程的),这就是说JS代码的执行顺序并不是从上至下按部就班完成的。大多数语言都是同步编程语言,比如现在我们有3行代码,那么系统一定是一行一行按顺序向下执行的,第一行执行完了,执行第二行,紧跟着最后执行第三行,你可能会说这不是废话吗?且慢,在JS里则不尽然,比如有3行代码,并不是排在最前面的代码就是最先执行完毕的,很有可能是最后一行语句最先执行完,然后排在最前面的那行反而是最后执行完毕的,所以我们说JS是异步编程语言

代码示例1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var fs = require("fs");
var c

function f(x) {
console.log(x)
}

function writeFile() {
fs.writeFile('input.txt', '我是通过fs.writeFile 写入文件的内容', function (err) {
if (!err) {
console.log("文件写入完毕!")
c = 1
}
});
}

c = 0
writeFile() // 函数内部执行了c = 1
f(c)

以上代码不难理解,就是设置一个全局变量c = 0,然后执行writeFile函数(也就是写入一个文件input.txt),这个函数里面有一行c = 1,函数执行完毕之后再跳出来调用f()函数,f()函数很简单,就是把打印一个变量,仅此而已。

按照同步的逻辑,首先c=0,然后调用writeFile函数,该函数里面有一句c = 1,最后再调用f(c),又因为调用writeFile()是在f(c)之前,所以c=1这条语句肯定是会被执行到,那么结果应该是打印1,但是万万想不到,结果竟然是0,明明我们在writeFile函数里我们重新对c进行了赋值,为什么结果还是0呢?

因为程序运行到writeFile()这一行的时候,是一个比较耗时的IO操作,JS碰到这种操作并不会停在原地一直等待直到函数执行完毕,而是直接运行下一条代码(即f(c)),而此时 c = 1这一行代码其实并没有被执行到,所以打印出来的结果还是0 !

那你肯定会说,要解决这个问题还不容易,我们把调用f(c)也放进writeFile函数里面不就行了呗!这样就能保证c = 1之后再调用f(c)了吧?没错,就这么简单:

代码示例2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var fs = require("fs");
var c

function f(x) {
console.log(x)
}

function writeFile() {
fs.writeFile('input.txt', '我是通过fs.writeFile 写入文件的内容', function (err) {
if (!err) {
console.log("文件写入完毕!")
c = 1
f(c)
}
});
}

c = 0
writeFile()

这个代码的逻辑不需要多说了吧,因为实在太简单了,就是把f(c)放进了writeFile()里面,那么c=1必然会被执行到,然后才执行f(c),不用多说,结果肯定是显示为1。但是改成这样并不完美,因为这么做就相当于将f()”焊死”在writeFile()里了,如果此处我最终想调用的函数不是f()而是别的其他函数咋整?难不成要写几个不同的writeFile(),而他们之间的区别仅仅是最后调用的那个函数不同?这样也太笨了吧,于是今天的主角:“关键字” callback 登场了。(准确地说callback并不真的是Javascript里的关键字,但是鉴于大家都约定成俗把callback这个单词作为回调函数的默认选择了,这里姑且就不严谨地称它为”关键字”吧)

代码示例3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var fs = require("fs");

function f(x) {
console.log(x)
}

function writeFile(callback) { //callback,表示这个参数不是一个普通变量,而是一个函数
fs.writeFile('input.txt', '我是通过fs.writeFile 写入文件的内容', function (err) {
if (!err) {
console.log("文件写入完毕!")
c = 1
callback(c) // 因为我们传进来的函数名是f(),所以此行相当于调用一次f(c)
}
});
}
var c = 0
writeFile(f) // 函数f作为一个参数传进writeFile函数

经过改造后的代码出现了两次callback,第一个callback出现在writeFile的形参里,起定义的作用,表示这个参数并不是一个普通变量,而是一个函数,也就是前面所说的重点1,即所谓的“以函数为参数”。 第二个callback出现在c = 1下面,表示此处“执行”从形参传递进来的那个函数。这样一来,writeFile()函数在执行完毕之后到底调用哪个函数就变“活”了,如果我们想writeFile()函数执行完之后并不是像第二个例子那样只能调用f(),而是还有别的函数比如说x() y() z(),那么只需要写成 writeFile(x),writeFile(y)… 就行了。

在大多数编程语言中,函数的形参总是从外向内传递参数,但在JS中,如果形参碰到“关键字” callback 则完全相反,它表示从内向外反向调用某个外部函数。

0.1.2 匿名的回调函数(主流用法)

有时候,我们会看到一些函数的形参列表里直接嵌套一个函数的情况,其本质上仍然是回调函数,因为没有了函数名,所以也称匿名函数。

如本例如果要写成这种风格的话就是长成这样了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var fs = require("fs");

function writeFile(callback) {
fs.writeFile('input.txt', '我是通过fs.writeFile 写入文件的内容', function (err) {
if (!err) {
console.log("文件写入完毕!")
c = 1
callback(c)
}
});
}
var c = 0
// 匿名的回调函数
writeFile(function (x) {
console.log(x)
})

writeFile()函数不变,只是在调用它的时候,直接将函数体嵌在参数列表里了,其作用跟上一个例子完全一样。其实在本例中,fs.writeFile函数后面也有一个匿名回调函数 function (err) {},这个函数表示当文件写入完毕后,就回调它,如果在写入过程中出现了错误,则通过变量err携带出来。事实上这种写法在JS里是出现频率最高的主流风格。

在JS里,当然也并非所有操作都是异步的,比如for循环,无论这个for循环需要耗时多长,系统也一定会等它转完之后才会执行下面的语句。会产生异步执行的操作大概有以下几种:

定时器、建立网络连接、读取网络流数据、向文件写入数据、Ajax提交、请求数据库服务,等等。

0.2 HTTP方法

  • GET
  • POST
  • PUT
  • HEAD
  • DELETE
  • PATCH
  • OPTIONS

最常用的两种方法是:GET 和 POST。

0.2.1 GET 方法

GET 用于从指定资源请求数据

GET 是最常见的 HTTP 方法之一。

请注意,查询字符串(名称/值对)是在 GET 请求的 URL 中发送的:

1
/test/demo_form.php?name1=value1&name2=value2

有关 GET 请求的其他一些注释:

  • GET 请求可被缓存
  • GET 请求保留在浏览器历史记录中
  • GET 请求可被收藏为书签
  • GET 请求不应在处理敏感数据时使用
  • GET 请求有长度限制
  • GET 请求只应当用于取回数据(不修改)

0.2.2 POST方法

POST 用于将数据发送到服务器来创建/更新资源

通过 POST 发送到服务器的数据存储在 HTTP 请求的请求主体中:

1
2
3
POST /test/demo_form.php HTTP/1.1
Host: w3school.com.cn
name1=value1&name2=value2

POST 是最常见的 HTTP 方法之一。

有关 POST 请求的其他一些注释:

  • POST 请求不会被缓存
  • POST 请求不会保留在浏览器历史记录中
  • POST 不能被收藏为书签
  • POST 请求对数据长度没有要求

0.2.3 其他方法

1) PUT方法

PUT 用于将数据发送到服务器来创建/更新资源。

POST 和 PUT之间的区别在于 PUT 请求是幂等的(idempotent)。也就是说,多次调用相同的 PUT 请求将始终产生相同的结果。相反,重复调用POST请求具有多次创建相同资源的副作用。

2) HEAD方法

HEAD 与 GET 几乎相同,但没有响应主体

换句话说,如果 GET /users 返回用户列表,那么 HEAD /users 将发出相同的请求,但不会返回用户列表。

HEAD 请求对于在实际发出 GET 请求之前(例如在下载大文件或响应正文之前)检查 GET 请求将返回的内容很有用

3) DELETE方法

DELETE 方法删除指定的资源。

4) OPTIONS方法

OPTIONS 方法描述目标资源的通信选项。

0.3 GET和POST的区别

0.3.1 标准答案

分类 GET POST
后退按钮/刷新 无害 数据会被重新提交(浏览器应该告知用户数据会被重新提交)。
书签 可收藏为书签 不可收藏为书签
缓存 能被缓存 不能缓存
编码类型 application/x-www-form-urlencoded application/x-www-form-urlencoded 或 multipart/form-data。为二进制数据使用多重编码。
历史 参数保留在浏览器历史中。 参数不会保存在浏览器历史中。
对数据长度的限制 是的。当发送数据时,GET 方法向 URL 添加数据;URL 的长度是受限制的(URL 的最大长度是 2048 个字符)。 无限制。
对数据类型的限制 只允许 ASCII 字符。 没有限制。也允许二进制数据。
安全性 与 POST 相比,GET 的安全性较差,因为所发送的数据是 URL 的一部分。在发送密码或其他敏感信息时绝不要使用 GET ! POST 比 GET 更安全,因为参数不会被保存在浏览器历史或 web 服务器日志中。
可见性 数据在 URL 中对所有人都是可见的。 数据不会显示在 URL 中。

所以从标准上来看,GET 和 POST 的区别如下:

  • GET 用于获取信息,是无副作用的,是幂等的,且可缓存
  • POST 用于修改服务器上的数据,有副作用,非幂等,不可缓存

0.3.2 报文区别

先下结论,GET 和 POST 方法没有实质区别,只是报文格式不同。

GET 和 POST 只是 HTTP 协议中两种请求方式,而 HTTP 协议是基于 TCP/IP 的应用层协议,无论 GET 还是 POST,用的都是同一个传输层协议,所以在传输上,没有区别。

报文格式上,不带参数时,最大区别就是第一行方法名不同:

  • POST方法请求报文第一行是这样的 POST /uri HTTP/1.1

  • GET方法请求报文第一行是这样的 GET /uri HTTP/1.1

是的,不带参数时他们的区别就仅仅是报文的前几个字符不同而已

带参数时报文的区别呢? 在约定中,GET 方法的参数应该放在 url 中,POST 方法参数应该放在 body 中

报文示例

举个例子,如果参数是name=qiming.c&age=22

GET 方法简约版报文是这样的:

1
2
GET /index.php?name=qiming.c&age=22 HTTP/1.1
Host: localhost

POST 方法简约版报文是这样的:

1
2
3
4
5
POST /index.php HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded

name=qiming.c&age=22

现在我们知道了两种方法本质上是 TCP 连接,没有差别,也就是说,如果我不按规范来也是可以的。我们可以在 URL 上写参数,然后方法使用 POST;也可以在 Body 写参数,然后方法使用 GET。当然,这需要服务端支持。

0.3.3 常见问题

GET 方法参数写法是固定的吗?

在约定中,我们的参数是写在 ? 后面,用 & 分割。

我们知道,解析报文的过程是通过获取 TCP 数据,用正则等工具从数据中获取 Header 和 Body,从而提取参数。

也就是说,我们可以自己约定参数的写法,只要服务端能够解释出来就行,一种比较流行的写法是 http://www.example.com/user/name/chengqm/age/22

POST 方法比 GET 方法安全?

按照网上大部分文章的解释,POST 比 GET 安全,因为数据在地址栏上不可见。

然而,从传输的角度来说,他们都是不安全的,因为 HTTP 在网络上是明文传输的,只要在网络节点上捉包,就能完整地获取数据报文。

要想安全传输,就只有加密,也就是 HTTPS。

GET 方法的长度限制是怎么回事?

在网上看到很多关于两者区别的文章都有这一条,提到浏览器地址栏输入的参数是有限的。

首先说明一点,HTTP 协议没有 Body 和 URL 的长度限制,对 URL 限制的大多是浏览器和服务器的原因。

浏览器原因就不说了,服务器是因为处理长 URL 要消耗比较多的资源,为了性能和安全(防止恶意构造长 URL 来攻击)考虑,会给 URL 长度加限制。

1 简介

  • AJAX 简介 AJAX 全称为 Asynchronous JavaScript And XML,就是异步的 JS 和 XML。

  • 通过 AJAX 可以在浏览器中向服务器发送异步请求,最大的优势:无刷新获取数据

  • AJAX 不是新的编程语言,而是一种将现有的标准组合在一起使用的新方式。

1.1 XML简介

  1. XML 可扩展标记语言Extensible Markup Language
  2. XML 被设计用来传输和存储数据。
  3. XML 和 HTML 类似,不同的是 HTML 中都是预定义标签,而 XML 中没有预定义标签, 全都是自定义标签,用来表示一些数据。

比如说我有一个学生数据: name = “孙悟空” ; age = 18 ; gender = “男” ,用xml表示:

1
2
3
4
5
<student>
<name>孙悟空</name>
<age>18</age>
<gender></gender>
</student>

但xml现在已经被 JSON 取代了(在前后端数据交互的领域内):

1
2
3
4
5
{
"name":"孙悟空"
"age":18
"gender":"男"
}

1.2 Ajax的特点

优点

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

缺点

  1. 没有浏览历史,不能回退
  2. 存在跨域问题(同源)
  3. SEO 不友好 (搜索引擎优化),例如爬虫弄爬不到网页内的数据,因为网页内数据是用ajax请求到后端,后端发送到前端,然后js动态创建出来的

1.3 HTTP简介

HTTP(hypertext transport protocol)协议『超文本传输协议』,协议详细规定了浏览器和万维网服务器之间互相通信的规则、约定、规则。

1.3.1 报文格式

请求报文格式

1
2
3
4
5
6
7
8
行   POST /s?ie=utf-8 HTTP/1.1 

头 Host: atguigu.com
Cookie: name=guigu
Content-type: application/x-www-form-urlencoded
User-Agent: chrome 83
空行
体 username=admin&password=admin
  • 请求行:请求方式(GET、POST…) URL HTTP版本
  • 请求体:GET为空,POST不为空

响应报文格式

1
2
3
4
5
6
7
8
9
10
11
12
13
行   HTTP/1.1 200 OK

头 Content-Type: text/html;charset=utf-8
Content-length: 2048
Content-encoding: gzip
空行
体 <html>
<head>
</head>
<body>
<h1>尚硅谷</h1>
</body>
</html>
  • 请求状态码:200,404,500…

1.3.2 Chrome网络控制台查看通信报文

  1. Network –> Hearders 请求头
  2. Network –> Response 响应体:通常返回的是html

以GET请求为例:General为请求行

image-20220202110823689

image-20220202110951672

1.4 环境准备

1.4.1 安装应用

  • 安装Nodejs
  • 安装Express

创建一个文件夹ajax,并在此处运行cmd

1
npm init --yes
1
npm i express

image-20220202111655320

1.4.2 启动Express服务器

ajax目录下新建一个demo.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 1.引入express
const express = require("express");

// 2.创建应用对象
const app = express();

// 3.创建路由规则
// request是对请求报文的封装
// response是对响应报文的封装
app.get("/", (request, response)=>{
// 设置响应
response.send("Hello Express!");
});

// 4.监听端口
app.listen(8000, ()=>{
console.log("服务已经启动,8000端口监听中...");
})

在终端中输入,启动服务:

1
node demo.js

执行结果:

image-20220202112719648

补充:JS的箭头函数

ES6标准新增了一种新的函数:Arrow Function(箭头函数)。

为什么叫Arrow Function?因为它的定义用的就是一个箭头:

1
x => x * x // 左侧为函数的形参列表,右侧为函数返回值

上面的箭头函数相当于:

1
2
3
function (x) {
return x * x;
}

箭头函数相当于匿名函数,并且简化了函数定义。箭头函数有两种格式,一种像上面的,只包含一个表达式,连{ ... }return都省略掉了。还有一种可以包含多条语句,这时候就不能省略{ ... }return

1
2
3
4
5
6
7
8
x => {
if (x > 0) {
return x * x;
}
else {
return - x * x;
}
}

如果参数不是一个,就需要用括号()括起来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 两个参数:
(x, y) => x * x + y * y

// 无参数:
() => 3.14

// 可变参数:
(x, y, ...rest) => {
var i, sum = x + y;
for (i=0; i<rest.length; i++) {
sum += rest[i];
}
return sum;
}

2 原生Ajax

2.1 案例准备

ajax下新建目录原生Ajax,并新建GET.htmlserver.js

  • GET.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ajax GET 请求</title>
<style>
#result {
width: 200px;
height: 100px;
border: solid 1px #90b;
}
</style>
</head>
<body>
<button>点击发送请求</button>
<div id="result"></div>
</body>
</html>
  • server.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 1.引入express
const express = require("express");

// 2.创建应用对象
const app = express();

// 3.创建路由规则
// request是对请求报文的封装
// response是对响应报文的封装
app.get("/server", (request, response)=>{
// 设置响应头,设置允许跨域
response.setHeader("Access-Control-Allow-Origin", "*");
// 设置响应体
response.send("Hello Ajax!");
});

// 4.监听端口
app.listen(8000, ()=>{
console.log("服务已经启动,8000端口监听中...");
})

image-20220202114824535

2.2 Ajax基本操作

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

2.2.1 GET请求

1) 代码实现
  • GET.html
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ajax GET 请求</title>
<style>
#result {
width: 200px;
height: 100px;
border: solid 1px #90b;
}
</style>
</head>
<body>
<button>点击发送请求</button>
<div id="result"></div>
<script>
// 获取button元素
const btn = document.getElementsByTagName("button")[0];
// 获取result元素
const result = document.getElementById('result');
// 绑定事件
btn.onclick = function() {
// 1.创建XMLHttpRequest对象
const xhr = new XMLHttpRequest();
// 2.初始化,设置请求方法GET和url
xhr.open('GET', 'http://127.0.0.1:8000/server');
// 3.发送
xhr.send();
// 4.事件绑定,处理服务端返回的结果(即响应response)
// readystate 是 xhr 对象中的属性,表示状态
xhr.onreadystatechange = function() {
// 判断,服务端返回了所有结果
if(xhr.readyState === 4) {
// 判断响应状态码,如果成功
if(xhr.status >= 200 && xhr.status < 300) {
// 处理结果 行 头 空行 体
console.log(xhr.status); // 响应状态码
console.log(xhr.statusText); // 响应状态字符串
console.log(xhr.getAllResponseHeaders); // 所有响应头
console.log(xhr.response); // 响应体
// 设置result的文本
result.innerHTML = xhr.response;
} else {

}
}
}
}
</script>
</body>
</html>
  • ajax请求状态 xhr.readyState
    • 0:请求未初始化,还没有调用 open()。
    • 1:请求已经建立,但是还没有发送,还没有调用 send()。
    • 2:请求已发送,正在处理中(通常现在可以从响应中获取内容头)。
    • 3:请求在处理中;通常响应中已有部分数据可用了,没有全部完成。
    • 4:响应已完成;您可以获取并使用服务器的响应了

点击按钮后的执行结果

  • 页面及控制台

image-20220202120740612

  • 网络

image-20220202121027427

2) 设置请求参数
1
xhr.open('GET', 'http://127.0.0.1:8000/server?a=100&b=200&c=300');

image-20220202121709645

2.2.2 POST请求

1) 代码实现
  • 新建文件POST.html
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
38
39
40
41
42
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ajax POST 请求</title>
<style>
#result {
width: 200px;
height: 100px;
border: solid 1px rgb(236, 45, 45);
}
</style>
</head>
<body>
<div id="result"></div>
<script>
// 获取元素对象
const res = document.getElementById('result');
// 绑定事件
res.addEventListener("mouseover", function() {
// 1.创建对象
const xhr = new XMLHttpRequest();
// 2.初始化
xhr.open('POST', 'http://127.0.0.1:8000/server');
// 3.发送
xhr.send();
// 4.事件绑定
xhr.onreadystatechange = function() {
// 判断 服务端返回了所有结果
if(xhr.readyState === 4) {
// 判断响应状态码
if(xhr.status >= 200 && xhr.status < 300) {
// 设置result的文本
result.innerHTML = xhr.response;
}
}
}
});
</script>
</body>
</html>
  • server.js新增路由规则,添加对post请求的跨域规则:
1
2
3
4
5
6
app.post("/server", (request, response)=>{
// 设置响应头,设置允许跨域
response.setHeader("Access-Control-Allow-Origin", "*");
// 设置响应体
response.send("Hello Ajax POST!");
});

执行结果

image-20220202122646906

2) 设置请求体
1
xhr.send('a=100&b=200&c=300');

image-20220202122958320

3) 设置请求头信息
  • POST.html
1
2
3
// 设置请求头
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
xhr.setRequestHeader('name', 'hongyi')
  • server.js
1
2
3
4
5
6
7
8
9
// 可以接受任意类型的请求
app.all("/server", (request, response)=>{
// 设置响应头,设置允许跨域
response.setHeader("Access-Control-Allow-Origin", "*");
// 响应头
response.setHeader("Access-Control-Allow-Headers", "*");
// 设置响应体
response.send("Hello Ajax POST!");
});

执行结果

image-20220202133748169

2.2.3 服务端响应json数据

需求:后端返回的响应体数据类型为json字符串,前端需要展示json字符串的值。

  • server.js
1
2
3
4
5
6
7
8
9
10
11
12
13
// 可以接受任意类型的请求
app.all("/json-server", (request, response)=>{
// 设置响应头,设置允许跨域
response.setHeader("Access-Control-Allow-Origin", "*");
// 响应一个数据
const data = {
name: 'hongyi'
}
// 对对象进行json字符串转换
let str = JSON.stringify(data)
// 设置响应体
response.send(str)
});
  • 新建JSON.html
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
38
39
40
41
42
43
44
45
46
47
48
49
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JSON响应</title>
<style>
#result {
width: 200px;
height: 100px;
border: solid 1px #89b;
}
</style>
</head>
<body>
<div id="result"></div>
<script>
// 获取元素对象
const res = document.getElementById('result');
// 绑定键盘按下事件
window.onkeydown = function () {
// 创建对象
const xhr = new XMLHttpRequest();
// 设置响应体数据的类型为json
xhr.responseType = 'json';
// 初始化
xhr.open('GET', 'http://127.0.0.1:8000/json-server')
// 发送
xhr.send();
// 事件绑定
xhr.onreadystatechange = function() {
// 判断 服务端返回了所有结果
if(xhr.readyState === 4) {
// 判断响应状态码
if(xhr.status >= 200 && xhr.status < 300) {
// 方式一:
// 手动对数据转换:注意response是后端传过来的响应体,格式为json字符串
// let data = JSON.parse(xhr.response);
// res.innerHTML = data.name;

// 方式二:先设置响应体数据的类型为json(见25行代码)
res.innerHTML = xhr.response.name
}
}
}
}
</script>
</body>
</html>

执行结果:

image-20220202135255768

2.3 nodemon热启动

运行指令安装nodemon:

1
npm install -g nodemon

之后再运行服务器时,运行执行:

1
nodemon server.js

image-20220202135751908

2.4 ie缓存问题

问题:在一些浏览器中(IE),由于缓存机制的存在,ajax 只会发送的第一次请求,剩余多次请求不会再发送给浏览器而是直接加载缓存中的数据。

解决方式:浏览器的缓存是根据 url 地址来记录的,所以我们只需要修改 url 地址,即可避免缓存问题。

1
xhr.open('GET', 'http://127.0.0.1:8000/ie?t=' + Date.now())

2.5 请求超时与网络异常处理

当后端对请求的响应时间过长,或者无网络时,前端进行的相应处理。

  • server.js
1
2
3
4
5
6
7
8
9
// 延时3s响应
app.get("/delay", (request, response)=>{
// 设置响应头,设置允许跨域
response.setHeader("Access-Control-Allow-Origin", "*");
setTimeout(() => {
// 设置响应体
response.send("延时响应");
}, 3000)
});
  • 新建timeout.html
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
38
39
40
41
42
43
44
45
46
47
48
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>请求超时与网络异常处理</title>
<style>
#result {
width: 200px;
height: 100px;
border: solid 1px #89b;
}
</style>
</head>
<body>
<button>点击发送请求</button>
<div id="result"></div>
<script>
const btn = document.getElementsByTagName('button')[0];
const res = document.querySelector('#result')
btn.addEventListener('click', function() {
const xhr = new XMLHttpRequest();
// 超时2s设置
xhr.timeout = 2000;
// 超时回调
xhr.ontimeout = function() {
alert('网络异常,请稍后重试');
}
// 网络异常回调
xhr.onerror = function() {
alert('网络异常');
}
xhr.open('GET', 'http://127.0.0.1:8000/delay');
xhr.send();
// 事件绑定
xhr.onreadystatechange = function() {
// 判断 服务端返回了所有结果
if(xhr.readyState === 4) {
// 判断响应状态码
if(xhr.status >= 200 && xhr.status < 300) {
res.innerHTML = xhr.response;
}
}
}
});
</script>
</body>
</html>

超时执行结果

image-20220202142212596

2.6 取消请求

在请求发出去后但是未响应完成时可以进行取消请求操作。

1
xhr.abort();
  • 新建cancel.html
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>取消请求</title>
</head>
<body>
<button>点击发送</button>
<button>点击取消</button>
<script>
// 获取元素对象
const btns = document.querySelectorAll('button');
let xhr = null;

btns[0].onclick = function() {
xhr = new XMLHttpRequest();
xhr.open('GET', 'http://127.0.0.1:8000/delay');
xhr.send();
}
// abort
btns[1].onclick = function() {
xhr.abort();
}
</script>
</body>
</html>

执行效果

先点击发送,再点击取消

image-20220202170051492

2.7 重复请求问题

利用之前2.6节中取消请求知识点,当点击时判断之前请求是否在发送中,如果是,则停止请求。

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>重复请求问题</title>
</head>
<body>
<button>点击发送</button>
<script>
// 获取元素对象
const btns = document.querySelectorAll('button');
let xhr = null;
// 标识变量,是否正在发送ajax请求
let isSending = false;
btns[0].onclick = function() {
// 先判断标识变量
if(isSending) xhr.abort();
xhr = new XMLHttpRequest();
// 修改标识变量
isSending = true;
xhr.open('GET', 'http://127.0.0.1:8000/delay');
xhr.send();
xhr.onreadystatechange = function() {
if(xhr.readyState === 4) {
isSending = false;
}
}
}
</script>
</body>
</html>

执行结果

image-20220202171104896

3 Ajax请求方式

3.1 JQuery方式

jQuery有三种发送请求方法:GET,POST和通用型请求

当你只是简单的请求数据,可以直接使用前两种方式请求,当你需要设置的东西较多的时候,可以使用$.ajax()方法。

3.1.1 GET请求和POST请求

语法格式:

1
2
$.get(请求url, 携带参数, 回调函数, 响应体接收格式)
// $.post(...)

例如:注意携带参数的格式。data为后端回传的数据

1
2
3
$.get('http://127.0.0.1:8000/jquery-server', {a:100, b:200}, function(data){
console.log(data);
}, 'json');

示例

新建一个文件jquery.html

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JQuery发送ajax请求</title>
<!-- 引入 Bootstrap和JQuery -->
<link crossorigin="anonymous" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<script crossorigin="anonymous" src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
</head>
<body>
<div class="container">
<h2 class="page-header">JQuery发送ajax请求</h2>
<button class="btn btn-primary">GET</button>
<button class="btn btn-danger">POST</button>
<button class="btn btn-info">通用型方法ajax</button>
</div>
<script>
// 发送get请求
$('button').eq(0).click(function() {
$.get('http://127.0.0.1:8000/jquery-server', {a:100, b:200}, function(data){
console.log(data);
}, 'json');
});
// 发送post请求
$('button').eq(1).click(function() {
$.post('http://127.0.0.1:8000/jquery-server', {a:100, b:200}, function(data){
console.log(data);
}, 'json');
});

</script>
</body>
</html>

server.js中新增路由规则:

1
2
3
4
5
6
7
8
9
10
11
12
// JQuery服务
app.all("/jquery-server", (request, response)=>{
// 设置响应头,设置允许跨域
response.setHeader("Access-Control-Allow-Origin", "*");
// 响应一个数据
const data = {
name: 'hongyi'
}
// 对对象进行字符串转换
let str = JSON.stringify(data)
response.send(str)
});

执行结果

image-20220202173114694

3.1.2 Ajax请求

语法格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$.ajax({
url: ,
data: ,
type: ,
dataType: ,
success: function = (){

},
error: function = (){

},
timeout:
....
});

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 发送通用型请求
$('button').eq(2).click(function() {
$.ajax({
// url
url: 'http://127.0.0.1:8000/jquery-server',
// 参数
data: {a:100, b:200},
// 请求类型
type: 'GET',
// 响应体接收格式
dataType: 'json',
// 成功的回调函数
success: function(data) {
console.log(data);
},
// 超时时间
timeout: 2000,
// 失败的回调
error: function() {
console.log("网络出错");
}
});

执行结果:

image-20220202174416293

3.2 Axios方式(常用)

Axios有三种发送请求方法:GET,POST和通用型请求(axios)

3.2.1 GET请求和POST请求

语法格式:

1
2
axios.get(URL, 可选参数)
axios.post(URL, 请求体参数, 可选参数)

示例

  • server.js中新增路由规则:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// axios服务
app.all("/axios-server", (request, response)=>{
// 设置响应头,设置允许跨域
response.setHeader("Access-Control-Allow-Origin", "*");
// 响应头
response.setHeader("Access-Control-Allow-Headers", "*");
// 响应一个数据
const data = {
name: 'hongyi'
}
// 对对象进行字符串转换
let str = JSON.stringify(data)
response.send(str)
});
  • 新建文件axios.html
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 引入axios -->
<script crossorigin="anonymous" src="https://cdn.bootcdn.net/ajax/libs/axios/0.25.0/axios.min.js"></script>
<title>axios 发送 Ajax请求</title>
</head>
<body>
<button>GET</button>
<button>POST</button>
<button>AJAX</button>

<script>
const btns = document.querySelectorAll('button');

// 配置 baseURL
axios.defaults.baseURL = 'http://127.0.0.1:8000';

btns[0].onclick = function() {
// GET请求
axios.get('/axios-server', {
// 参数
params: {
id: 100,
vip: 7
}
}).then(value => {
console.log(value);
});
}

btns[1].onclick = function() {
// POST请求
axios.post('/axios-server', {
username: 'admin',
password: 'admin'
},{
// 参数
params: {
id: 100,
vip: 9
}
}).then(value => {
console.log(value);
});
}
</script>
</body>
</html>

执行结果

  • GET请求:

image-20220203154728453

image-20220203154737498

  • POST请求

image-20220203154834000

3.2.2 Ajax请求

示例

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
btns[2].onclick = function() {
// ajax请求
axios({
// 请求方法
method: 'POST',
// url
url: 'axios-server',
// url参数
params: {
level:30
},
// 头信息
headers: {
a: 100,
b: 200
},
// 请求体参数
data: {
username: 'admin',
password: '12345678'
}
}).then(response => {
console.log(response.status);
console.log(response.statusText);
console.log(response.headers);
console.log(response.data);
});
}

执行结果:

image-20220203155558339

image-20220203155622389

3.3 Fetch方式

语法格式:

1
2
3
fetch(URL, {请求消息的参数}).then(resnponse => {
// 响应信息的回调函数
});

示例

1
2
3
4
5
6
7
8
9
10
11
12
btn.onclick = function() {
fetch('http://127.0.0.1:8000/fetch-server', {
method: 'POST',
headers: {
name: 'hongyi'
},
// 请求体
body: 'username=admin&passowrd=12345678'
}).then(response => {
console.log(response);
});
}

执行结果:

image-20220203160345903

image-20220203160429579

4 跨域

4.1 跨域概述

什么是跨域:

  • 一个网页向另一个不同域名/不同协议/不同端口的网页请求资源,这就是跨域。
  • 跨域原因产生:在当前域名请求网站中,默认不允许通过ajax请求发送其他域名

为什么会产生跨域请求

  • 因为浏览器使用了同源策略

什么是同源策略

  • 同源策略是Netscape提出的一个著名的安全策略,现在所有支持JavaScript的浏览器都会使用这个策略。同源策略是浏览器最核心也最基本的安全功能,如果缺少同源策略,浏览器的正常功能可能受到影响。可以说web是构建在同源策略的基础之上的,浏览器只是针对同源策略的一种实现。
  • 同源: 协议、域名、端口号 必须完全相同。 违背同源策略就是跨域

为什么浏览器要使用同源策略

  • 是为了保证用户的信息安全,防止恶意网站窃取数据,如果网页之间不满足同源要求,将不能:
    • 共享Cookie、LocalStorage、IndexDB
    • 获取DOM
    • AJAX请求不能发送

跨域的五个解决方式

  1. 前端使用jsonp (不推荐使用)
  2. 后台Http请求转发
  3. 后台配置同源Cors (推荐)
  4. 使用SpringCloud网关
  5. 使用nginx做转发 (推荐)

4.2 JSONP

  • JSONP 是什么

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

  • JSONP 怎么工作的?

在网页有一些标签天生具有跨域能力,比如:img link iframe script。 JSONP 就是利用 script 标签的跨域能力来发送请求的。

例如,之前引入axios函数库的时候,就是利用的script标签的跨域能力进行的:

1
2
<!-- 引入axios -->
<script crossorigin="anonymous" src="https://cdn.bootcdn.net/ajax/libs/axios/0.25.0/axios.min.js"></script>

4.2.1 script标签的跨域能力

  • server.js中新增路由规则:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// jsonp服务
app.all("/jsonp-server", (request, response)=>{
// 响应一个数据
const data = {
name: 'hongyi'
}
// 对对象进行字符串转换
let str = JSON.stringify(data)
// 返回结果
// 注意这个handle函数,前端必须声明
// 而str是后端向前端发送的数据
response.send(`handle(${str})`)
// 也可以response.send('console.log(str)')之类的可以被解析的js代码
});
  • 新建test.html
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>script跨域能力演示</title>
<style>
#res {
width: 300px;
height: 100px;
border: solid 1px red;
}
</style>
</head>
<body>
<div id="res"></div>
<script>
// 处理后端传过来的数据
function handle(data) {
// 获取标签
const res = document.getElementById('res');
res.innerHTML = data.name;
}
</script>
<script src="http://127.0.0.1:8000/jsonp-server"></script>
</body>
</html>

执行结果:

image-20220203164346442

注意到:前端的URL地址为file:///E:/develop/study/project-study/ajax/%E8%B7%A8%E5%9F%9F/test.html,而请求的地址url

http://127.0.0.1:8000/jsonp-server,显而易见是跨域,但是script标签具有跨域能力,因此执行可以成功。

另外必须注意,后端发送回的字符串,必须是前端可以解析的js代码,而不能是纯字符串。此处返回的就是对handle函数的调用:handle('console.log(....)')

image-20220203165926696

4.2.2 原生JSONP实践

需求:在输入框内输入任意用户名,失去焦点后发送ajax请求,后端返回判断是否用户名重复的结果,假设都为重复,此时输入框变红。

  • 新增路由规则
1
2
3
4
5
6
7
8
9
10
11
// 检测用户名是否存在
app.all("/check-username", (request, response)=>{
// 响应一个数据
const data = {
exist: 1,
msg: '用户名已经存在'
}
// 对对象进行字符串转换
let str = JSON.stringify(data)
response.send(`handle(${str})`)
});
  • 新建文件index.html
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
38
39
40
41
42
43
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>原生JSONP实践</title>
<style>
#res {
width: 300px;
height: 100px;
border: solid 1px red;
}
</style>
</head>
<body>
用户名:<input type="text" id="username">
<p></p>
<script>
// 获取元素
const input = document.getElementById('username');
const p = document.querySelector('p');
// 声明handle函数
function handle(data) {
input.style.border = 'solid 1px red';
// 修改p标签的提示文本
p.innerHTML = data.msg;
}

// 绑定事件
input.onblur = function() {
// 获取用户输入值
let username = this.value;
// 向后端发送请求,检测重复
// 1.创建script标签
const script = document.createElement('script');
// 2.设置标签的src属性
script.src = 'http://127.0.0.1:8000/check-username';
// 3.将script插入到文档中
document.body.appendChild(script);
}
</script>
</body>
</html>

执行结果:

image-20220203170043750

响应消息的响应体数据:

image-20220203170123986

注意是一段可被解析的js代码

4.3 CORS

CORS文档链接

  • CORS是什么

CORS(Cross-Origin Resource Sharing),跨域资源共享。CORS 是官方的跨域解决方案,它的特点是不需要在客户端做任何特殊的操作,完全在服务器中进行处理,支持 get 和 post 请求。跨域资源共享标准新增了一组 HTTP 首部字段,允许服务器声明哪些源站通过浏览器有权限访问哪些资源。

  • CORS是怎么工作的

CORS 是通过设置一个响应头来告诉浏览器,该请求允许跨域,浏览器收到该响应以后就会对响应放行。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
app.all('/cors-server', (request, response) => {
//设置响应头

//响应首部中可以携带一个 Access-Control-Allow-Origin 字段
response.setHeader("Access-Control-Allow-Origin", "*");
//Access-Control-Allow-Headers 首部字段用于预检请求的响应。其指明了实际请求中允许携带的首部字
response.setHeader("Access-Control-Allow-Headers", '*');
//Access-Control-Allow-Methods 首部字段用于预检请求的响应。其指明了实际请求所允许使用的 HTTP
response.setHeader("Access-Control-Allow-Method", '*');
// response.setHeader("Access-Control-Allow-Origin", "http://127.0.0.1:5500");
response.send('hello CORS');
});