Ajax学习记录
该文章仅作为笔者的学习笔记。
参考资料
尚硅谷
什么是AJAX
AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)。
AJAX 不是新的编程语言,而是一种使用现有标准的新方法。
AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容,也就是说页面无刷新获取数据。
普通的HTML,我们可以使用form
表单来发送请求,但是呢?它会引起页面的跳转,会刷新页面。但是很多时候我们并不需要页面的跳转和刷新。
在日常生活中有很多这样的需求,比如说页面下拉获取新闻,这肯定是不需要刷新界面的。
AJAX 不需要任何浏览器插件,但需要用户允许JavaScript在浏览器上执行。
- 不刷新页面更新网页
- 在页面加载后从服务器请求数据
- 在页面加载后从服务器接收数据
- 在后台向服务器发送数据
AJAX的工作流程
XML简介
XML 指可扩展标记语言(eXtensible Markup Language)。
XML 被设计用来传输和存储数据。
XLM和HTML类似,不同的是HTML都是预定义的标签,而XML中没有预定以的标签,全部都是自定义标签,用来表示一些数据。
<?xml version="1.0" encoding="UTF-8"?>
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>
可以看到,所以内容都必须被标签包括,而且都是自定义的标签。可以看出xml的体积很大。
现在基本已经被JSON取代了。
{
"note":[
"to":"Tove",
"from":"jani",
"heading":"Reminder",
"body":"Don't forget me this weekend!"
]
}
用JSON表示结构清晰,数据量也不大,解析也很方便JSON.paser()
和JSON.stringify()
。
AJAX的特点
优点
- 可以无需刷新页面与服务器进行数据交互
- 允许根据用户事件来更新部分界面
缺点
-
没有浏览历史,不能回退
如果使用表单,那么它会跳转到另外一个页面,那么这样就可以形成历史记录,但是对于AJXA来说,它是无刷新获取数据的,那么就没有形成历史记录。
-
存在跨域问题
-
SEO不友好
JavaScript原生AJAX的使用
使用的对象
XMLHttpRequest
,AJAX的所有操作都是通过该对象进行的。
使用步骤
搭建一个简单的本地服务器
使用express + Nodejs 搭建
const express = require('express');
const app = express();
// 暴露静态资源
// 若想要与服务器交互,不能在使用本地打开或者liveserver打开了。他们使用的协议或者端口是不一样的,会产生跨域问题
app.use(express.static(__dirname + '/src'))
app.get('/test_get',(request,response)=>{
console.log("有请求接入");
response.send("Hello 啊");
})
app.listen(80 ,(err)=>{
if(!err){
console.log('测试ajax的服务器开启成功');
console.log('测试地址是:http://localhost/1.ajax小试牛刀.html');
}
});
初体验
html
<!DOCTYPE html>
<html>
<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>
<style>
#content {
margin-top: 10px;
width: 300px;
height: 100px;
border: 1px solid red;
}
</style>
</head>
<body>
<h1>该页面是测试ajax的基础使用</h1>
<button id="btn">点我发送(原生js-ajax-get)</button>
<div id="content">
</div>
<script>
const btn = document.querySelector("#btn");
const content = document.querySelector("#content");
btn.onclick = () =>{
// 发送ajax请求
//1. 创建xhr实例对象
const xhr = new XMLHttpRequest();
// xhr 内部有5种状态,值分别为 0 1 2 3 4
// 4 就是数据完整回来
// xhr实例对象,在实例完成的那一刻,状态为0
/*
5中状态值:0,1,2,3,4
实例刚出来就是0
在发送请求的时候,xhr中的状态一直带改变
0:初始状态
1:open已经调用了,但是send还没有调用,此时可以修改请求头内容
2: send已经调用了,无法修改请求头了
3:已近回来一部分数据了, 如果是较小的数据会在此阶段一次性接受
,较大的数据有待进一步接受,但是响应头一定会被接受。
4: 所有数据响应回来
*/
xhr.onreadystatechange = ()=>{
// 函数体
if(xhr.readyState === 3 ){
console.log("3时接受到的数据",xhr.response);
console.log("3时接受到的数据",xhr.getAllResponseHeaders);// 返回响应头的所有信息
console.log("3时接受到的数据",xhr.getResponseHeader);// 返回响应头中的一条信息,可以指定
console.log(xhr.getResponseHeader("content-length"));//获取响应体数据的信息
}
if(xhr.readyState === 4 && xhr.status >= 200 && xhr.status < 300){
console.log("数据回来了");
console.log(xhr.response);
content.innerHTML = xhr.response;
}
}
//2. 指定发送请求的url 和 method
xhr.open("GET","http://localhost/test_get");
//3. 发送请求
xhr.send();
}
</script>
</body>
</html>
创建XMLHttpRquest对象
在IE7+及其其他浏览器创建此对象
const xhr = new XMLHttpRequest();
在IE5和IE6中创建此对象
const xhr=new ActiveXObject("Microsoft.XMLHTTP");
var xmlhttp;
if (window.XMLHttpRequest)
{
// IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码
xmlhttp=new XMLHttpRequest();
}
else
{
// IE6, IE5 浏览器执行代码
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}
AJAX向服务器发送请求
如果想要向服务器发送请求,那么就需要使用open() 和 send() 方法
例如
xhr.open("GET","http://localhost/test_get");
xhr.send();
open(method,url,async) and send(string)
方法 | 描述 |
---|---|
open(method,url,async) | 规定请求的类型、URL 以及是否异步处理请求。method: 请求的类型,GET或者POST或其他 |
url: 文件在服务器上的问题(也就是路径) | |
async:true(异步)、false(异步) | |
send(string) | 将请求发送到服务器。对于string来说它是当请求方法时POST的时候的请求体字符串。 |
GET请求
对于GET的请求有两种参数:query 和 params
对于GET请求来说它是没有请求体的
querystring 是查询字符串:它的编码方式是 urlencoded 编码方式,也就是 key=value&key=value
的方式。
使用express获取查询字符串对象的方式
requset.query
params也是get 的参数,它的格式是 /xx/xxx, 这很容易与路径相混淆,在express中它是这样处理的
app.get('/:id/:name',(req,res)=>{
console.log(req.params.id); console.log(req.params.name);
})
里面也可以使用正则表达式,或者- 和 .
'/:name-:dd' => /fujiaxu-dasd\
'/:name.dd' = > /fujiaxu.xxx
'/\^s\' => /s
一般代码
const btn = document.querySelector("#btn");
const content = document.querySelector("#content");
btn.onclick = () =>{
// 发送ajax请求
//1. 创建xhr实例对象
const xhr = new XMLHttpRequest();
// 绑定监听
// on 当。。。。。的时候
// ready 准备
// state 状态
// change 改变
// 当准备的状态发生改变
// xhr 内部有5种状态,值分别为 0 1 2 3 4
// 4 就是数据完整回来
// xhr实例对象,在实例完成的那一刻,状态为0
xhr.onreadystatechange = ()=>{
// 函数体
if(xhr.readyState === 4){
console.log("数据回来了");
console.log(xhr.response);
content.innerHTML = xhr.response;
}
}
//2. 指定发送请求的url 和 method
xhr.open("GET","http://localhost/test_get");
//3. 发送请求
xhr.send();
}
POST请求
POST的请求也有query、params,但是最常用的还是请求体。在AJAX中使用send(string)方法发送。
请求体最常用的类型是 urlencoded 和 JSON。
对于发送urlendcoded类型的请求体时,请使用 setRequestHeader() 来添加 HTTP 头
xhr.setRequestHeader("Content-type","application/x-www-form-ur");
xhr.send("name=fujiaxu&&age=17");
对于发送JSON类型的请求体时,请使用 setRequestHeader() 来添加 HTTP 头
xhr.setRequestHeader("Content-type","application/json");
let obj = {
"name":"fujiaxu",
"age" : 18
}
xhr.send(JSON.stringify(obj));
对于使用express框架开启的服务需要加载一些中间件或者使用body-parser
。要不然无法解析。
app.use(express.urlencoded({extended:true}));// 解析urlencoded的请求体
app.use(express.json());// 解析JSON类型的请求体。
AJAX响应
ajax获取和解析数据
获取响应体数据
xhr.response;
xhr.responseText //获得字符串形式的响应数据。
xhr.responseXML //获得 XML 形式的响应数据。
解析JSON
可以使用JSON.parse(xhr.response),但是如果返回的数据不是JSON,那么将会报错**。**
我们可以这样做xhr.responseType = “json”;必须是在返回数据前就设定好
这样有如果返回的数据不是json,那么将返回结果null,不会报错
readyState
直接看教程。
AJAX – onreadystatechange 事件 | 菜鸟教程 (runoob.com)
解决IE中get请求缓存问题
304状态码
(5条消息) HTTP 304状态码的详细讲解_胡杰的专栏-CSDN博客_304状态码
一文读懂http缓存(超详细) - 简书 (jianshu.com)
304状态码的意思是浏览器走的是协商缓存。但是IE浏览器如果看到请求的同一个东西,那么它将不会询问服务器是否是同一内容,直接走缓存,这么造成服务器修改内容无效。
解决方法:
首先ie不支持模板字符串和箭头函数。
既然ie是通过每次请求的url是否相同来判断请求内容是否相同,那么我们可以给它加一个时间戳的参数
"http://localhost/xxx?t="+Date.now()
这样就可以解决了。
POST请求是不会有这个问题的,这是当时设计的问题。ie认为既然GET所有的参数(query和params)都在url中,那么url没有变化就意味着内容没有变化。
AJAX请求异常和超时的处理
错误处理
xhr有一个处理错误的api
xhr.onerr = ()=>{
}
超时处理
这样说还等待返回结果,但是等待时间位置,我们也不可能无限等待,所以要设置一个最大的等待时间。
xhr.timeout = 2000;// 2s // 设置超时等待的时间
// 设置超时的回调函数
xhr.ontimeout = ()=>{
alert("你的网网速实在是太垃圾了");
}
如果等了2s,数据还没有回来,那么就不再等待了,直接取消
取消请求
必须要在数据全完返回之前取消请求,要不然无法起作用。
xhr.abort();// 终止请求
避免多次重复请求
如果多次请求,那么将会出现重复请求,消耗大量的资源,我们可以通过一些设置来取消最后一次之前的所有请求。使用flag来判断上次请求是否成功,若还没有成功,则取消上一次的请求,只需要本次请求的结果。
Jquery中的AJAX
[$.ajax(url,settings]) | jQuery API 3.2 中文文档 | jQuery API 在线手册 (cuishifeng.cn)
jQuery ajax - ajax() 方法 (w3school.com.cn)
回调地狱:回调函数里面套回调函数,使用promise技术可以解决。
同源策略(same origin policy)
浏览器的同源策略 - Web 安全 | MDN (mozilla.org)
浏览器同源政策及其规避方法 - 阮一峰的网络日志 (ruanyifeng.com)
跨域资源共享 CORS 详解 - 阮一峰的网络日志 (ruanyifeng.com)
同源策略规定了不同域
不能获取Cookie、LocalStorage 和 IndexDB 无法读取。
对于cookie来说可以使用
document.domain
来允许子域安全访问其父域时,您需要在父域和子域中设置 document.domain 为相同的值。这是必要的,即使这样做只是将父域设置回其原始值。不这样做可能会导致权限错误。不能获取ajax返回的数据,可以发送请求,但是响应被拦截
不能获取DOM
解决ajax跨域问题
JSONP
这其实是一种规定跨域问题的方法,而没有真正的解决问题。不需要调用XMLHttpRquest对象来发送请求了。
只能用于get,因script标签只能使用get。
前端定义函数,后端调用函数。
利用的原理:
<script> <img> <link> <style> <form>等这些标签不会受到同源策略的限制,可以发送请求,并取得响应的结果。
它的基本思想是,网页通过添加一个<script>
元素,向服务器请求JSON数据,这种做法不受同源政策限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。
前端页面写法:
<body>
<button id="btn">按钮</button>
<script type="text/javascript">
var btn = document.getElementById('btn');
btn.onclick = function () {
//1. 创建一个script标签
var script = document.createElement('script');
//2. 设置回调函数
window.getData = function (data) {
console.log(data);//拿到数据
}
//3. 设置script标签src属性,填写跨域请求的地址
script.src = 'http://localhost:3000/jsonp?callback=getData';// 在参数中给出回调函数的名字
//4. 将script标签添加到body中生效
document.body.appendChild(script);
//5.不影响整体DOM结构,删除script标签
document.body.removeChild(script);
}
</script>
</body>
后端写法(express框架):
app.get('/jsonp', (req, res) => {
//解构赋值获取请求参数
const {callback} = req.query
//去数据库查找对应数据
const data = [{name: 'tom', age: 18}, {name: 'jerry', age: 20}];
res.send(callback + '(' + JSON.stringify(data) + ')');
})
其原理就是在页面中先设置一个全局的函数来得到数据,在script标签中得到后端发来的数据(也就是JavaScript代码),自动执行这样就可以调用预先设置好的函数来会的内容了。
作为参数的JSON数据被视为JavaScript对象,而不是字符串,因此避免了使用JSON.parse
的步骤。这是为什么呢?因为本来发过来就是一堆字符串,一种猜测,浏览器解析的时候它就变成了js代码,那么JSON.stringify(data)的数据在里面自然就变成了对象了。
比如说
“fun({“a”:123})” 在js 代码里面就是 fun({“a”:123}) 这肯定就是一个对象啊,
所以我们再返回数据的时候应该想象到在js代码中,我们的数据会变成什么样子。
Websocket方式解决
以下文字来自于:浏览器同源政策及其规避方法 - 阮一峰的网络日志 (ruanyifeng.com)
WebSocket是一种通信协议,使用ws://
(非加密)和wss://
(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。
下面是一个例子,浏览器发出的WebSocket请求的头信息(摘自*)。
GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13 Origin: http://example.com
上面代码中,有一个字段是Origin
,表示该请求的请求源(origin),即发自哪个域名。
正是因为有了Origin
这个字段,所以WebSocket才没有实行同源政策。因为服务器可以根据这个字段,判断是否许可本次通信。如果该域名在白名单内,服务器就会做出如下回应。
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= Sec-WebSocket-Protocol: chat
CORS
跨域资源共享 CORS 详解 - 阮一峰的网络日志 (ruanyifeng.com)
CORS是跨源资源分享(Cross-Origin Resource Sharing)的缩写。它是W3C标准,是跨源AJAX请求的根本解决方法。相比JSONP只能发GET
请求,CORS允许任何类型的请求。
它的特点是:不需要要在客户端进行任何的操作,完全在服务器中进行。
整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
如何解决的?
cors就是利用了响应头,设置响应头来告诉浏览器,改请求允许跨域,浏览器收到申请后对该响应放行。而不是对request拦截。
出现跨域问题,可以看到返回的信息中,有一句话说的是没有Access-Control-Allow-Origin
头。
在服务器端设置响应头(express框架下)
app.get("/test",(request,response)=>{
repsonse.setHeader("Access-Control-Allow-Origin","具体网站的地址");
repsonse.setHeader("Access-Control-Allow- Origin","http://127.0.0.1:5500");// 协议+主机名+端口号
repsonse.setHeader("Access-Control-Allow-Origin","*");//任何网站都可以在此路由中拿到数据
})
这样设置只能解决一部分的问题,对于复杂请求起不到作用,而且使用xhr.getAllResponseHeaders
无法所以的响应头,Access-COntrol-Allow-Origin","http://127.0.0.1:5500
,也无法拿到。
所以要设置
repsonse.setHeader("Access-Control-Expose-Header","*");// 暴露所以的响应头
对于http的复杂请求,比如PUT
和DELETE
这两种请求,或者Content-Type
字段的类型是application/json
。
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest
请求,否则就报错。
请求的方式是
就是OPTIONS
请求。在后端的路由中我们需要设置
app.options("/test",(requset,response)=>{
repsonse.setHeader("Access-Control-Allow-Origin","具体网站的地址");
repsonse.setHeader("Access-Control-Allow- Origin","http://127.0.0.1:5500");// 协议+主机名+端口号
repsonse.setHeader("Access-Control-Allow-Origin","*");//任何网站都可以在此路由中拿到数据
repsonse.setHeader("Access-Control-Allow-Methods","*");//任何请求都可以在此路由中拿到数据
})
当然在express还可以使用一个中间件cors
来简化这些步骤。
var express = require('express')
var cors = require('cors')
var app = express()
app.use(cors())
app.get('/products/:id', function (req, res, next) {
res.json({msg: 'This is CORS-enabled for all origins!'})
})
app.listen(80, function () {
console.log('CORS-enabled web server listening on port 80')
})
具体的使用方法cors - npm (npmjs.com)