一、XML(可扩展标记语言)
Ajax 最开始向服务器请求数据,数据格式就是 XML。客户端接收到数据,提取后处理数据。现在使用的数据格式一般都是 JSON,相较于 XML 来说,数据更加简洁,并且数据的转换也更加的容易,可以借助 JSON 的 API 快速将 JSON 数据转化成 js 对象,灵活度远胜于 XML。
- 同样是标记语言,XML 一般是用来保存、存储、传输数据
- HTML 则是用来向网页中呈现数据
- HTML 内都是一些预定义标签,XML 中没有预定义标签
二、HTTP
1. 请求报文
- 行:各种请求类型(GET、POST、DELETE ... )+ URL 路径(可能还会带有一些参数) + HTTP 版本
- 头:(Key: Value) 对请求体内容做一个简单的描述
- Host: baidu.com
- Cookie: name=baidu
- Content-type: application/x-www-form-urlencoded
- User-Agent: chrome 83
- ...
- 空行(必须有)
- 体:
- GET 请求请求体为空,POST 请求请求体可以不为空。
- 例如: username=admin&password=admin
2. 响应报文
- 行:HTTP 版本 + 响应状态码 + 响应状态字符串
- 头:对响应体内容做一个相关的描述
- Content-Type: text/html;charset=utf-8
- Content-length: 2048
- Content-encoding: gzip
- ...
- 空行:(必须有)
- 体:主要的返回的结果(html 标签结构)
- 浏览器接到结果后,将响应体结果提取出来,对内容做一个解析,在页面做一个渲染和显示
三、发送请求的方式
1. 原生 JS 发送 XMLHttpRequest(XHR)请求
- XHR 是早出现的向后端发送请求的技术
- 原生 js 发送的请求,并不是 Ajax 请求,而是 XHR 请求。在 JQuery 封装过后,才有了 Ajax 请求。
XHR 发送请求格式:
- 创建一个服务器
// 创建一个服务器
// 1. 引入express
const express = require('express');
// 2. 创建一个应用对象
const app = express();
// 3. 创建路由规则
// request 是对请求报文的封装
// response 是对响应报文的封装
app.get('/server', (request, response)=>{
// 设置响应头 设置允许跨域
response.setHeader('Access-Content-Allow-Origen', '*');
// 设置响应体
response.send('Hello Ajax GET');
});
app.post('/server', (request, response)=>{
// 设置响应头 设置允许跨域
response.setHeader('Access-Content-Allow-Origen', '*');
// 设置响应体
response.send('Hello Ajax POST');
});
// 4. 监听端口启动服务
app.listen(8000, ()=>{
console.log("服务已经启动,8000 端口监听中 ... ");
});
-
客户端发送请求
// 客户端发送get请求
// 获取button 元素
const btn = document.getElementByTagName('button')[0];
// 获取
const result = document.getElementById("result");
// 绑定事件
btn.onclick = function(){
// 1. 创建对象
const xhr = new XMLHttpRequest();
// 2. 初始化 设置请求方法和url
xhr.open('GET', 'http://127.0.0.1:8000/server?a=100&b=200');
// 3. 发送
xhr.send();
// 4.事件绑定 处理服务器返回的结果
// on when 当 ... 的时候
// readystate 是xhr对象中的属性,表示状态 0 1 2 3 4
// 0 初始化的属性状态
// 1 open方法已经调用完毕
// 2 send方法调用完毕
// 3 服务端返回了部分结果
// 4 服务端返回了所有的结果
// change 改变的时候触发
// 下面的函数会被触发四次,状态每改变一次,就会触发一次
xhr.onreadystatechange = function(){
// 判断(服务端返回了所有的结果)
if(xhr.readyState === 4){
// 判断响应状态码
if(xhr.state >= 200 && xhr.state < 300){
// 处理结果
// console.log(xhr.status); // 状态码
// console.log(xhr.statusText); // 状态字符串
// console.log(xhr.getAllResponseHeaders()); // 所有响应头
// console.log(xhr.response); // 响应体
// 设置result 的文本
result.innerHTML = xhr.response;
}
}
}
}
// 客户端发送post请求
const result = document.getElementById("result");
result.addEventListener("mouseover", function(){
const xhr = new XMLHttpRequest();
xhr.open('POST', 'http://127.0.0.1:8000/server');
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
xhr.send('a=100&b=200');
//xhr.send('a:100&b:200');
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
if(xhr.state >= 200 && xhr.state < 300){
result.innerHTML = xhr.response;
}
}
}
})
2. 使用 JQuery 发送 Ajax 请求:
Ajax:Asynchronous Javascript And XML 异步 JavaScript 和 XML
优点:
- 在网页不刷新的情况下,更新网页的部分内容。
缺点:
- 针对后端 MVC 模型编程,与前端框架的 MVVM 风格不符
- 使用 Ajax 必须引入 JQuery,但 JQuery 太大,个性化打包又不能享受 CDN 加速服务
- 不符合关注分离原则(关注分离:部分发生了变化,不会影响其他部分。够清晰地识别出哪些部分需要改变。)
- SEO(Search Engine Optimization 搜索引擎优化)不友好【通过 Ajax 异步请求获取数据,通过 js 动态创建到页面,因此源代码(响应体)中并不存在商品信息,而爬虫只能爬取源代码中的数据】
// 1. 发送get请求
$('button').eq(0).click(function(){
$.get('http://127.0.0.1:8000/jquery-server', {a: 100, b: 200}, function(){
console.log(data);
})
})
// 2. 发送post请求
$('button').eq(1).click(function(){
$.post('http://127.0.0.1:8000/jquery-server', {a: 100, b: 200}, function(){
console.log(data);
})
}, 'json')
// 第四个是一个可选参数:'json',表示数据类型,会自动将服务器返回的json类型的字符串转化为js对象
// 3. 发送通用请求
$('button').eq(2).click(function(){
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('出错了 ... ')
}
// 头信息
header: {
c: 300, // 这是两个自定义的头
d: 400
}
}, 'json')
// 发送通用请求时,需要在在服务器上更改这两处配置:
// 1. 接受请求方式为all
// 2. 设置允许跨域
app.all('/server', (request, response)=>{
// 设置响应头 设置允许跨域
response.setHeader('Access-Content-Allow-Origen', '*');
// 设置响应体
response.send('Hello Ajax');
});
补充:MVC 与 MVVM 有什么区别?
- MVC 是后端的分层开发概念,其站在整个项目的视角,对项目结构进行一个划分
- M 指 Model,只要主要负责数据的 CRUD
- V 指 View 视图层,也面的渲染
- C 指 Controller 控制层,包括了 Router 与 Controller
- MVVM 则是站在前端网页的角度,对视图和数据进行一个划分
- M 指 Model 层,里面包含了各种数据
- V 指 View 层,也就是呈现给用户的页面
- VM 表示 ViewModel 层,它在数据和页面之间做调度,一方发生改变,对另一方做同步改变。MVVM 的数据双向绑定就是由 VM 层提供的
3. axios 发送 Ajax 请求
优点:
- 基于 Promise 的 http 请求库,返回一个 Promise 对象,本质上也是对 XHR 的封装。
- 客户端支持防止 CSRF
- 提供了一些并发请求的接口(重要,方便了很多)
- axios 提供了并发的封装,没有 fetch 的各种问题,而且体积也较小
PS:防止CSRF(跨站请求伪造):就是让我们的每个请求都带一个从 cookie 中拿到的 key,根据浏览器同源策略,假冒的网站是拿不到你 cookie 中的 key 的,这样,后台就可以轻松辨别出这个请求是否是用户在假冒网站上的误导输入,从而采取正确的策略。
并发:指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。
并行:在操作系统中,一组程序 按独立异步 的速度执行,无论从微观还是宏观,程序都是 一起执行的。
// 1. 借助axios发送get请求
const btns = document.querySelectorAll('button');
axios.defaults.baseURL = 'http://127.0.0.1:8000';
btns[0].onclick = function(){
axios.get('/axios-server', {
// url参数
params: {
id: 100,
vip: 10
}
// 请求头信息
headers: {
name: 'zhangsan',
nickName: 'fawaikuangtu'
}
}).then(value => {
console.log(value);
})
}
// 2. 借助axios发送post请求
btns[1].onclick = function(){
axios.post('/axios-server', {
// 请求体信息
username: 'zhangsan',
password: 'luoxiang'
}, {
// url参数
params: {
id: 100,
vip: 10
}
// 请求头信息
headers: {
name: 'zhangsan',
nickName: 'fawaikuangtu'
}
}).then(res=> {
console.log(res);
})
}
// 3. 借助axios发送post请求
btns[2].onclick = function(){
axios({
method: 'POST',
url: '/axios-server',
// 请求体信息
data:{
username: 'zhangsan',
password: 'luoxiang'
},
// url参数
params: {
id: 100,
vip: 10
}
// 请求头信息
headers: {
name: 'zhangsan',
nickName: 'fawaikuangtu'
}
}).then(res=> {
console.log(res);
console.log(res.status);
console.log(res.statusText);
console.log(res.headers);
})
}
4. 使用 fetch 发送异步请求
特点:
fetch 不是对 XHR 的封装,也没有使用 XHR 对象,在本质上,应该说是 XHR 的同类,因为它们都是底层的发送请求的方法。
优点:
- 更符合关注分离,没有将输入、输出、状态跟踪很杂在一起
- 基于 Promise,返回一个 Promise 对象。
缺点:
- 过于底层,对400、500的状态码不会 reject,还需要再次封装
- 默认不带 cookie,需要添加配置项 fetch(url, {credentials: 'include'})
- 不支持 abort,无法直接阻断请求,不支持超时控制,使用 setTimeout、Promise.reject 无法阻止请求,请求过程依旧在后台运行
- 无法像 XHR 一样检测请求进度
- 兼容性不好,IE 不支持。
PS:XHR send 之后,本质请求已经发送, 进入网络传输了,流量浪费已经发生,abort 只是在我们未来某个时机调用,不想要这个请求了,即使远端服务器返回的数据已经被浏览器拿到,在我们调用了 abort 方法后,也可以不执行回调。
// 借助fetch发送post请求
btns[2].onclick = function(){
fetch('http://127.0.0.1:8000/fetch-server?vip=10', {
method: 'POST',
// 请求体信息
body: 'username=zhangsan&password=luoxiang',
// 请求头信息
headers: {
name: 'zhangsan',
nickName: 'fawaikuangtu'
}
}).then(res => {
return res.text();
// return res.json(); // 自动帮我们把数据解析成js对象
}).then(res => {
console.log(res);
})
}
四、同源策略
请求的 url 与 Ajax 请求的目标资源的 url 协议、域名、端口完全一致时,就被认为是跨域
Ajax 默认遵循同源策略。
五、解决跨域的方案
1. JSONP(JSON with Padding)解决跨域
- 只支持 get 请求。
- 借助页面的一些天生可以跨域的 script 标签实现(img、link、iframe、script)
在 VSCode 中,Open In Default Browser 与 Open with Live Server 打开一个 html 文件,二者有什么区别?
- 借助 Open with Live Server 打开网页,地址栏显示的是一个地址,使用的是 http 协议
- 而通过 Open In Default Browser 打开网页时,地址栏显示的是文件存放的地址,使用的是 file 协议。
1)我们先来看一个常规路径引入的方式,这个方式并不会引发跨域:
// a.js
const data = {
name: 'Tom'
}
console.log(data);
<!-- b.html -->
<body>
<script src="./js/a.js" ></script>
</body>
在这个时候,不论使用的是哪一种方式来打开 html 文件,都不会产生跨域,因为始终使用的都是同一种协议:
- 使用 Open with Live Server 打开文件:
- 使用 Open In Default Browser 打开文件:
2)我们可以创新一下,在 b.html 中,a.js 文件的路径,修改为 a.js 对应的 http 地址,然后使用 Open In Default Browser 打开 html 文件(file 协议),来产生跨域
<body>
<script src="http://127.0.0.1:5500/src/a.js"></script>
</body>
惊讶地发现,两个文件使用了不同的协议,但是引用成功!
其次,通过 <script> 标签发送请求获取的数据,必须是一串被 ' ' 包裹的 js 代码 。
3)我们再来用原生的 JSONP 来模拟一个真实的场景
服务器:
app.all('/check-username', (req, res) => {
const data = {
exist: 1,
msg: '用户名已经存在'
};
// 将数据转化为字符串
let str = JSON.stringify(data);
// 返回结果
res.end(`handle(${str})`);
})
客户端:
<!-- b.html -->
<body>
用户名:<input type="text" id="username">
<p></p>
<script>
// 获取 input、p 元素
const input = document.querySelector('input');
const p = document.querySelector('p');
function handle(data){
input.style.border = "solid 1px #f00";
p.innerHTML = data.msg;
}
// 绑定事件
input.onblur = function(){
// 获取用户输入
let username = this.value;
// 1. 创建script标签
const srcipt = document.createElement('script');
// 2. 设置标签的 src 属性
script.src = 'http://127.0.0.1:5500/check-username';
// 3. 将script插入到文档中
document.body.appendChild(script);
}
</script>
</body>
2. JQuery 发送 JSONP 请求
这种方式需要我们在 url 后面拼接一个 callback=?,这是格式要求
服务器:
app.all('/jquery-jsonp-server', (req, res) => {
const data = {
name: '张三',
nickName: '法外狂徒'
};
// 将数据转化为字符串
let str = JSON.stringify(data);
// 接收callback参数
let cb = request.query.callback;
// 返回结果
res.end(`${cb}(${str})`);
})
客户端:
$('button').eq(0).click(function(){
$.getJSON('http://127.0.0.1:5500/jquery-jsonp-server?callback=?', function(){
$('#result').html(`
姓名:${data.name},<br>
绰号:${data.nickName}
`)
})
})
3. CORS(跨域资源共享)
- 这是一个官方的跨域解决方案
- 原理是不需要在客户端做任何操作,告诉服务器,哪些源站通过浏览器有权访问哪些资源
我们在前面其实也一直在使用这个方法:
// 3. 创建路由规则
// request 是对请求报文的封装
// response 是对响应报文的封装
app.get('/server', (request, response)=>{
// ------------------- 这里就是CORS策略 -------------------
// 设置响应头 设置允许跨域
response.setHeader('Access-Content-Allow-Origen', '*');
// -------------------------------------------------------
// 设置响应体
response.send('Hello Ajax GET');
});
值表示允许访问的网页的地址,例如可以写 "http://127.0.0.1:5000",就表示从这里来的请求都有权力请求我们这个服务。也可以在这里写一个 * ,表示通配,所有的ip的都可以向我们发送请求
我们一般会再附带两个设置:
response.setHeader('Access-Content-Allow-Origen', '*'); // 哪个页面都可以
response.setHeader('Access-Content-Allow-Headers', '*'); // 头信息可以自定义
response.setHeader('Access-Content-Allow-Method', '*'); // 请求方法可以选择任意方式
参考视频: