Ajax学习笔记 学习来源:尚硅谷
笔记参考:努力学习的汪
0 补充知识 0.1 回调函数Callback 0.1.1 概述 首先要明白两个重点:
函数可以作为一个参数在另一个函数中被调用。
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 cfunction f (x ) { console .log (x) } function writeFile ( ) { fs.writeFile ('input.txt' , '我是通过fs.writeFile 写入文件的内容' , function (err ) { if (!err) { console .log ("文件写入完毕!" ) c = 1 } }); } c = 0 writeFile () 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 ) { fs.writeFile ('input.txt' , '我是通过fs.writeFile 写入文件的内容' , function (err ) { if (!err) { console .log ("文件写入完毕!" ) c = 1 callback (c) } }); } var c = 0 writeFile (f)
经过改造后的代码出现了两次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,用的都是同一个传输层协议,所以在传输上,没有区别。
报文格式上,不带参数时,最大区别就是第一行方法名不同:
是的,不带参数时他们的区别就仅仅是报文的前几个字符不同而已
带参数时报文的区别呢? 在约定中,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 : localhostContent-Type : application/x-www-form-urlencodedname =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简介
XML 可扩展标记语言Extensible Markup Language
。
XML 被设计用来传输和存储数据。
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的特点
优点
可以无需刷新页面而与服务器端进行通信。
允许根据用户事件来更新部分页面内容。
缺点
没有浏览历史,不能回退
存在跨域问题(同源)
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>
1.3.2 Chrome网络控制台查看通信报文
Network –> Hearders 请求头
Network –> Response 响应体:通常返回的是html
以GET请求为例:General
为请求行
1.4 环境准备 1.4.1 安装应用
创建一个文件夹ajax
,并在此处运行cmd
:
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 const express = require ("express" );const app = express ();app.get ("/" , (request, response )=> { response.send ("Hello Express!" ); }); app.listen (8000 , ()=> { console .log ("服务已经启动,8000端口监听中..." ); })
在终端中输入,启动服务:
执行结果:
补充:JS的箭头函数
ES6标准新增了一种新的函数:Arrow Function(箭头函数)。
为什么叫Arrow Function?因为它的定义用的就是一个箭头:
上面的箭头函数相当于:
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.html
和server.js
。
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 >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const express = require ("express" );const app = express ();app.get ("/server" , (request, response )=> { response.setHeader ("Access-Control-Allow-Origin" , "*" ); response.send ("Hello Ajax!" ); }); app.listen (8000 , ()=> { console .log ("服务已经启动,8000端口监听中..." ); })
2.2 Ajax基本操作 XMLHttpRequest
,AJAX 的所有操作都是通过该对象进行的。
2.2.1 GET请求 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 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 > const btn = document .getElementsByTagName ("button" )[0 ]; const result = document .getElementById ('result' ); btn.onclick = function ( ) { const xhr = new XMLHttpRequest (); xhr.open ('GET' , 'http://127.0.0.1:8000/server' ); xhr.send (); 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.innerHTML = xhr.response ; } else { } } } } </script > </body > </html >
ajax请求状态 xhr.readyState
:
0:请求未初始化,还没有调用 open()。
1:请求已经建立,但是还没有发送,还没有调用 send()。
2:请求已发送,正在处理中(通常现在可以从响应中获取内容头)。
3:请求在处理中;通常响应中已有部分数据可用了,没有全部完成。
4:响应已完成;您可以获取并使用服务器的响应了
点击按钮后的执行结果
2) 设置请求参数 1 xhr.open ('GET' , 'http://127.0.0.1:8000/server?a=100&b=200&c=300' );
2.2.2 POST请求 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 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 ( ) { const xhr = new XMLHttpRequest (); xhr.open ('POST' , 'http://127.0.0.1:8000/server' ); xhr.send (); xhr.onreadystatechange = function ( ) { if (xhr.readyState === 4 ) { if (xhr.status >= 200 && xhr.status < 300 ) { 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!" ); });
执行结果
2) 设置请求体 1 xhr.send ('a=100&b=200&c=300' );
3) 设置请求头信息
1 2 3 xhr.setRequestHeader ('Content-Type' , 'application/x-www-form-urlencoded' ) xhr.setRequestHeader ('name' , 'hongyi' )
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!" ); });
执行结果
2.2.3 服务端响应json数据 需求:后端返回的响应体数据类型为json字符串,前端需要展示json字符串的值。
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' } let str = JSON .stringify (data) response.send (str) });
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 (); 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 ) { res.innerHTML = xhr.response .name } } } } </script > </body > </html >
执行结果:
2.3 nodemon热启动 运行指令安装nodemon:
之后再运行服务器时,运行执行:
2.4 ie缓存问题 问题:在一些浏览器中(IE),由于缓存机制
的存在,ajax 只会发送的第一次请求,剩余多次请求不会再发送给浏览器而是直接加载缓存中的数据。
解决方式:浏览器的缓存是根据 url 地址来记录的,所以我们只需要修改 url 地址,即可避免缓存问题。
1 xhr.open ('GET' , 'http://127.0.0.1:8000/ie?t=' + Date .now ())
2.5 请求超时与网络异常处理 当后端对请求的响应时间过长,或者无网络时,前端进行的相应处理。
1 2 3 4 5 6 7 8 9 app.get ("/delay" , (request, response )=> { response.setHeader ("Access-Control-Allow-Origin" , "*" ); setTimeout (() => { response.send ("延时响应" ); }, 3000 ) });
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 (); 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 >
超时执行结果
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 <!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 (); } btns[1 ].onclick = function ( ) { xhr.abort (); } </script > </body > </html >
执行效果
先点击发送,再点击取消
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 ; 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 >
执行结果
3 Ajax请求方式 3.1 JQuery方式 jQuery有三种发送请求方法:GET,POST和通用型请求
当你只是简单的请求数据,可以直接使用前两种方式请求,当你需要设置的东西较多的时候,可以使用$.ajax()
方法。
3.1.1 GET请求和POST请求 语法格式:
1 2 $.get (请求url, 携带参数, 回调函数, 响应体接收格式)
例如:注意携带参数的格式。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 > <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 > $('button' ).eq (0 ).click (function ( ) { $.get ('http://127.0.0.1:8000/jquery-server' , {a :100 , b :200 }, function (data ){ console .log (data); }, 'json' ); }); $('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 app.all ("/jquery-server" , (request, response )=> { response.setHeader ("Access-Control-Allow-Origin" , "*" ); const data = { name : 'hongyi' } let str = JSON .stringify (data) response.send (str) });
执行结果
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 : '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 ("网络出错" ); } });
执行结果:
3.2 Axios方式(常用) Axios有三种发送请求方法:GET,POST和通用型请求(axios)
3.2.1 GET请求和POST请求 语法格式:
1 2 axios.get (URL , 可选参数) axios.post (URL , 请求体参数, 可选参数)
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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) });
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" > <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' ); axios.defaults .baseURL = 'http://127.0.0.1:8000' ; btns[0 ].onclick = function ( ) { axios.get ('/axios-server' , { params : { id : 100 , vip : 7 } }).then (value => { console .log (value); }); } btns[1 ].onclick = function ( ) { axios.post ('/axios-server' , { username : 'admin' , password : 'admin' },{ params : { id : 100 , vip : 9 } }).then (value => { console .log (value); }); } </script > </body > </html >
执行结果
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 ( ) { axios ({ method : 'POST' , url : 'axios-server' , 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 ); }); }
执行结果:
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); }); }
执行结果:
4 跨域 4.1 跨域概述 什么是跨域:
一个网页向另一个不同域名/不同协议/不同端口的网页请求资源,这就是跨域。
跨域原因产生:在当前域名请求网站中,默认不允许通过ajax请求发送其他域名 。
为什么会产生跨域请求
什么是同源策略
同源策略是Netscape提出的一个著名的安全策略,现在所有支持JavaScript的浏览器都会使用这个策略。同源策略是浏览器最核心也最基本的安全功能,如果缺少同源策略,浏览器的正常功能可能受到影响。可以说web是构建在同源策略的基础之上的,浏览器只是针对同源策略的一种实现。
同源: 协议、域名、端口号 必须完全相同。 违背同源策略就是跨域
。
为什么浏览器要使用同源策略
是为了保证用户的信息安全,防止恶意网站窃取数据,如果网页之间不满足同源要求,将不能:
共享Cookie、LocalStorage、IndexDB
获取DOM
AJAX请求不能发送
跨域的五个解决方式
前端使用jsonp (不推荐使用)
后台Http请求转发
后台配置同源Cors (推荐)
使用SpringCloud网关
使用nginx做转发 (推荐)
4.2 JSONP
JSONP(JSON with Padding
),是一个非官方的跨域解决方案,纯粹凭借程序员的聪明 才智开发出来,只支持 get 请求 。
在网页有一些标签天生具有跨域能力,比如:img
link
iframe
script
。 JSONP 就是利用 script 标签的跨域能力 来发送请求的。
例如,之前引入axios函数库的时候,就是利用的script
标签的跨域能力进行的:
1 2 <script crossorigin ="anonymous" src ="https://cdn.bootcdn.net/ajax/libs/axios/0.25.0/axios.min.js" > </script >
4.2.1 script标签的跨域能力
1 2 3 4 5 6 7 8 9 10 11 12 13 14 app.all ("/jsonp-server" , (request, response )=> { const data = { name : 'hongyi' } let str = JSON .stringify (data) response.send (`handle(${str} )` ) });
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 >
执行结果:
注意到:前端的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(....)')
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} )` ) });
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' ); function handle (data ) { input.style .border = 'solid 1px red' ; p.innerHTML = data.msg ; } input.onblur = function ( ) { let username = this .value ; const script = document .createElement ('script' ); script.src = 'http://127.0.0.1:8000/check-username' ; document .body .appendChild (script); } </script > </body > </html >
执行结果:
响应消息的响应体数据:
注意是一段可被解析的js代码
4.3 CORS CORS文档链接
CORS(Cross-Origin Resource Sharing
),跨域资源共享
。CORS 是官方的跨域解决方案 ,它的特点是不需要在客户端做任何特殊的操作,完全在服务器中进行处理 ,支持 get 和 post 请求。跨域资源共享标准新增了一组 HTTP 首部字段,允许服务器声明哪些源站通过浏览器有权限访问哪些资源。
CORS 是通过设置一个响应头来告诉浏览器,该请求允许跨域,浏览器收到该响应以后就会对响应放行。
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 app.all ('/cors-server' , (request, response ) => { response.setHeader ("Access-Control-Allow-Origin" , "*" ); response.setHeader ("Access-Control-Allow-Headers" , '*' ); response.setHeader ("Access-Control-Allow-Method" , '*' ); response.send ('hello CORS' ); });