浏览器的通信能力
- 浏览器可以代替用户完成
http
请求,代替用户解析响应结果,所以称之为用户代理user agent
在网络层面,浏览器拥有的两大核心能力
- 自动发出请求的能力
- 自动解析响应的能力
- 如上图所示,也可以不通过浏览器,直接发送请求,但是响应的结果不是开发者想看到的,那么浏览器的可以帮助开发者发送请求,并且解析响应的内容
自动发出请求的能力
当以下事情发生时,浏览器会代替用户自动发出
http
请求
- 用户在地址栏输入了一个
url
地址并按下回车,浏览器会自动解析URL
,并发出一个GET
请求,同时抛弃当前页面 - 用户点击页面中的
a
元素,浏览器会拿到a
元素的href
地址,并发出GET
请求,同时抛弃当前页面 - 用户点击提交按钮
<button type="submit">...</button>
,浏览器会获取按钮所在的<form>
元素的action
地址,同时拿到method
属性值,最后把表单数据组织到请求体,发出指定方法的请求,同时抛弃当前页面 - 解析
HTML
时遇到<link>
、<img>
、<script>
、<video>
、<audio>
等元素,浏览器会获取到对应的地址,并发出GET
请求 - 用户点击了刷新,浏览器会获取到当前页面的地址,以及当前页面的请求方法,重新发送请求,同时抛弃当前页面
服务器和浏览器的约定:发送
GET
请求,不附带请求体,该默认行为逐步造成GET
和POST
的差异
- 浏览器在发送
GET
请求时,不会附带请求体 GET
请求的传递信息量有限(URL
长度有限),适合传递少量数据;POST
请求的传递信息量没有限制,适合传输大量数据GET
请求只能传递ASCII
数据,若遇到非ASCII
数据则需进行编码;POST
请求没有限制- 大部分
GET
请求传递的数据都附带在path
参数中,能够通过分享地址完整重现页面,但同时暴露了数据,若有敏感数据传递,则不该使用GET
请求,至少不该放到path
中 POST
不会被保存到浏览器的历史记录中,历史记录的本质是复现请求,所以点开浏览器的历史记录都是通过GET
方式进行请求- 刷新页面时,若当前的页面是通过
POST
请求得到的,则浏览器会提示用户是否重新提交;若是GET
请求得到的页面则没有提示
自动解析响应的能力
- 浏览器不仅能发送请求,还能针对服务器的各种响应结果做出不同的自动处理
常见的响应处理
-
识别响应码:浏览器能够自动识别响应码,当出现特殊响应码时浏览器会自动完成处理,如
301、302
-
自动分析响应头的
Content-Type
,并进行不同处理text/plain
: 普通的纯文本,浏览器通常会将响应体原封不动的显示到页面上text/html
:html
文档,浏览器通常会将响应体作为页面进行渲染text/javascript
或application/javascript
:JavaScript
代码,浏览器通常会使用JavaScript
执行引擎进行解析执行text/css
:css
代码,浏览器会将它视为样式image/jpeg
: 浏览器会将它视为jpg
图片application/octet-stream
: 二进制数据,会触发浏览器下载功能attachment
: 附件,会触发下载功能,该值应放到Content-Disposition
中
Ajax的由来
- 浏览器本身就具备网络通信的能力,但在早期浏览器并没有把这个能力开放给
JavaScript
- 最早是微软在
IE
浏览器中把这一能力向JavaScript
开放,让JavaScript
可以在代码中实现发送请求,且不会刷新页面,这项技术在 2005 年被正式命名为AJAX
(Asynchronous JavaScript And XML>
AJAX
是指在web
应用程序中异步向服务器发送请求,实现方式有两种
功能点 | XHR | Fetch |
---|---|---|
基本的请求能力 | ✅ | ✅ |
基本的获取响应能力 | ✅ | ✅ |
监控请求进度 | ✅ | ❌ |
监控响应进度 | ✅ | ✅ |
Service Worker 中是否可用 | ❌ | ✅ |
控制 cookie 的携带 | ❌ | ✅ |
控制重定向 | ❌ | ✅ |
请求取消 | ✅ | ✅ |
自定义 referrer | ❌ | ✅ |
流 | ❌ | ✅ |
API 风格 | Event | Promise |
活跃度 | 停止更新 | 不断更新 |
XHR进度监控
- 首先准备一个上传文件的控件,这里以上传图片为例
/* 控制预览图和进度的显示与隐藏 */
.upload.select .upload-select {
display: block;
}
.upload.select .preview {
display: none;
}
.upload.progress .upload-progress {
display: block;
}
.upload.result .upload-result {
display: block;
}
<div class="upload select">
<!-- 文件上传控件 -->
<div class="upload-select">
<input type="file" />
</div>
<!-- 展示进度 -->
<div class="upload-progress" style="--percent: 0">
<div class="progress-bar"></div>
</div>
<!-- 展示预览的图片 -->
<img alt="" class="preview" />
</div>
- 点击后获取图片文件,并展示预览图和上传进度
function showArea(areaName) {
$('.upload').className = `upload ${areaName}`;
}
// 监听change事件,选中文件发生变化
$('.upload-select input').onchange = (e) => {
// 获取图片文件对象
const file = e.target.files[0];
// 使用图片文件读取器展示文件预览图
const reader = new FileReader();
// 将图片文件数据读取成base64
reader.readAsDataURL(file);
// 监听图片文件读取完成事件
reader.onload = (e) => {
// 读取完成
$('.preview').src = e.target.result;
展示需要上传的预览图
showArea('progress');
// 上传文件
uploadFile(file);
};
};
- 上传到服务器,并实时更改上传进度
function uploadFile(file) {
// 设置当前上传进度
setProgress(0);
// 创建xhr实例
const xhr = new XMLHttpRequest();
// 打开一个请求
xhr.open('POST', 'http://localhost:9527/upload/single');
// 生成 multipart/form-data 格式的请求体
const form = new FormData();
// 构建一条请求体(其中file是文件对象)
form.append('avatar', file);
// 监听请求进度
xhr.upload.onprogress = (e) => {
console.log(e.loaded, e.total);
// 计算请求进度,loaded是当前已经上传部分,total是总大小
const percent = Math.floor((e.loaded / e.total) * 100);
// 设置进度
setProgress(percent);
};
// 监听上传完成
xhr.onload = () => {
showArea('result');
};
// 发送请求
xhr.send(form);
}
// 设置进度的函数
function setProgress(value) {
$('.upload-progress').style.setProperty('--percent', value);
}