前面的http范围是比较广的,而前端工程师大部分是要和浏览器打交道,所以主要研究浏览器的网络通信能力。
1. 用户代理
浏览器可以代替用户完成http请求,代替用户解析响应结果,所以我们称之为用户代理user agent
。 在网络层面,对于前端开发者,必须要知道的浏览器两大核心能力:自动发出请求的能力 和 自动解析响应的能力。
1.自动发出请求的能力
当一些事情发生的时候,浏览器会代替用户自动发出http请求,常见的包括:
-
用户在地址栏输入了一个url地址,并按下了回车。
浏览器会自动解析url,并发送一个GET请求,同时抛弃当前页面。
-
当用户点击了页面中的a元素。
浏览器会拿到a元素的href地址,并发出一个GET请求,同时抛弃当前页面。
PS: 看我们写的路径和浏览器输出的路径,发现浏览器把路径补全了,形成一个完整的url地址。那我们的路径简化写法就有两种:
绝对路径
和相对路径
。- 绝对路径:与当前页面url的path没有任何关系,这个path如何改变都不会影响绝对路径生成的结果。比如
/home.html
,在生成url时,会借用当前页面的协议和域名,然后拼接起来,所以什么path的不会影响绝对路径的结果。 - 相对路径:相对的是当前页面的path部分。如果当前页面路径
http://www.aaa.com/a/b/1.html
,相对路径是./3.html
。其中./
表示当前路径的最后一个斜杠,那就是相对路径替换上去,最终的url就变成http://www.aaa.com/a/b/3.html
。同理,../
表示当前路径的倒数第二个斜杠。
- 绝对路径:与当前页面url的path没有任何关系,这个path如何改变都不会影响绝对路径生成的结果。比如
-
当用户点击了提交按钮<button type="submit"></button>
。浏览器会获取按钮所在的
form
元素,拿到它的action
属性地址,同时拿到它的method
属性组织到请求体中,发出指定方法
的请求,同时抛弃当前页面。PS: 这种提交方式不常用了。但是还是建议在有提交功能的页面里使用
form
,因为最直观的感受就是按下回车键能触发请求。如果不用form
,需要回车键,那就得给input绑定键盘事件,比较麻烦。html<form> <p> <input type="text" name="loginId" /> </p> <p> <input type="password" name="loginPwd" /> </p> <button type="submit">提交</button> </form> <script> const form = document.querySelector("form"); form.onsubmit = (e) => { e.preventDefault(); // 发请求 }; </script>
-
当解析html时遇到<link> <script> <img> <video> <audio>等元素
。浏览器会拿到对应的地址,发出GET请求。
-
当用户点击了刷新
。浏览器会拿到当前页面的地址,以及当前页面的请求方法,重新发送一次请求,同时抛弃当前页面。
PS:浏览器和服务器有着一个约定:当发送GET请求时,不会附带请求体。
这个约定深刻地影响着后续的前后端各种应用。
1.浏览器在发送GET请求时,不会附带请求体;
2.GET请求的传递信息量有限,适合传递少量数据;POST请求的传递信息量是没有限制的,适合传输大量数据。
3.GET请求只能传递ASCII数据,遇到非ASCII数据需要进行编码;POST请求没有限制。
4.大部分GET请求传递的数据都附带在path参数中,能够通过分享地址完整的重现页面,但同时也暴露了数据,若有敏感数据传递,不应该使用GET请求,至少不应该放到path中。
5.POST不会被保存到浏览器的历史记录中。
6.刷新页面时,若当前的页面是通过POST请求得到的,则浏览器会提示用是否重新提交。若是GET请求得到的页面则没有提示。
2.自动解析响应的能力
浏览器不仅能发送请求,还能够针对服务器的各种响应结果做出不同的自动处理。
-
识别响应码
浏览器能够自动识别响应码,当出现一些特殊的响应码时,浏览器会自动完成处理,比如
301、302
。 -
根据响应结果做不同的处理
浏览器能够自动分析响应头中的
Content-Type
,根据不同的值进行不同的处理,比如:text/plain
:普通的纯文本,浏览器通常会将响应体原封不动的显示在页面上。text/html
:html文档,浏览器通常会将响应体作为页面进行渲染。text/javascript
或application/javascript
:js代码,浏览器通常会使用JS执行引擎将它解析执行。text/css
:css代码,浏览器会将它视为样式。text/jpeg
:浏览器会将它视为jpeg图片。application/octet-stream
:二进制数据,attechment
:附件,会触发下载功能。该值和其他值不同,应放到Content-Disposition
中。
3.基本流程
2. AJAX
浏览本身就具有网络通信能力,但在早期,浏览器并没有把这个能力开放给JS。
最早是微软在IE浏览器中把这一能力向JS开放,让JS可以在代码中实现发送请求,并不会刷新页面。这项技术在2005年被正式命名为AJAX(Asynchronous Javascript And Xml
)。
AJAX就是指在web应用程序中异步向服务器发送请求。它的实现方式有两种,XMLHttpRequest
和fetch
3. 实战
1. 请求并获取响应数据
js
//获取英雄列表
async function loadHeroes() {
const res = await fetch("https://study.duyiedu.com/api/herolist"); //第一次等待,拿到响应头
const body = await res.json(); //第二次等待,拿到响应体 json() 为需要的响应体的格式
const data = body.data; //英雄数组
document.querySelector(".list").innerHTML = data
.map(
(h) => `
<li>
<a href="https://pvp.com/web201605/herodetail/${h.ename}.shtml" target="_blank">
<img src="https://game.gtimg.cn/images/yxzj/img201606/heroimg/${h.ename}/${h.ename}.jpg" alt="" />
<span>${h.cname}</span>
</a>
</li>
`
)
.join("");
}
loadHeroes();
2. 上传文件并监控进度
fetch
没有这个能力,所以只能用XHR
。
-
准备好html。
html<div class="upload select"> <div class="upload-select"> <input type="file" /> </div> <div class="upload-progress" style="--percent: 20"> <div class="progress-bar"> <button>取消</button> </div> </div> <div class="upload-result"> <button>X</button> </div> <img src="./files/small.jpg" alt="" class="preview" /> </div>
有三个类
select
、progress
和result
控制三个部分的显示与隐藏。
-
设置css 核心就是使用自定义属性
--percent
控制百分比大小与进度条的长度。 百分比是用伪类::after
来实现的,用到了css计数器counter-reset。css.progress-bar::before { counter-reset: progress var(--percent); //progress为自定义名字,第二个值为初始值 content: counter(progress) '%'; //使用计数器 position: absolute; left: 50%; top: -20px; transform: translateX(-50%); -webkit-transform: translateX(-50%); -moz-transform: translateX(-50%); -ms-transform: translateX(-50%); -o-transform: translateX(-50%); } .progress-bar::after { content: ''; position: absolute; left: 0; border-radius: inherit; width: calc(1% * var(--percent)); //设置进度条滑块的长度 height: 100%; background: var(--primary-color); }
-
js逻辑
js/** * @description: 切换类型,展示不同进度 * @param {String} areaName * @return {*} */ function showArea(areaName) { doms.container.className = `upload ${areaName}`; } /** * @description: 设置文件的上传进度值 * @param {Number} value * @return {*} */ function setProgress(value) { doms.progress.style.setProperty("--percent", value); } //获取文件并上传 doms.selectFile.onchange = (e) => { const file = e.target.files[0]; showArea("progress"); setProgress(0); };
到此,已经能够拿到文件,并且把文件的预览区(
progress
)显示出来,重置上传进度。下一步需要拿到这张图片的dataUrl,渲染到预览区。js//获取文件并上传 doms.selectFile.onchange = (e) => { showArea("progress"); setProgress(0); cons const reader = new FileReader(); // 创建文件读取器 // 读取完成后触发该事件 reader.onload = (e) => { doms.img.src = e.target.result; }; reader.readAsDataURL(file); // 读取文件t file = e.target.files[0]; };
-
上传文件
js/** * @description: ajax上传文件 * @param {any} file * @return {*} */ function upload(file) { const xhr = new XMLHttpRequest(); const url = ""; xhr.open("POST", url); xhr.setRequestHeader("Content-Type", "multipart/form-data;boundary=line"); //设置请求体 }
这是基本的流程,请求行、请求头、请求体,其中请求体需要用到文件的二进制。那原始请求中,需要用到
ArrayBuffer
,将数据一点一点的拼接进去,这是比较麻烦的。所以我们使用浏览器提供的api,方便拼接数据。jsfunction upload(file) { const xhr = new XMLHttpRequest(); const url = "http://localhost:9527/upload/single"; xhr.open("POST", url); //设置请求体 const formData = new FormData(); formData.append("picture", file); //拼接请求体 xhr.send(formData); }
现在图片已经是能上传成功了,然后我们需要监听图片的响应完成与进度。
jsfunction upload(file) { const xhr = new XMLHttpRequest(); const url = "http://localhost:9527/upload/single"; xhr.open("POST", url); //上传进度 xhr.upload.onprogress = (e) => { //loaded 已上传字节数 //total 总字节数 setProgress(Math.floor((e.loaded / e.total) * 100)); }; //响应完成 xhr.onload = () => { showArea("result"); }; //设置请求体 const formData = new FormData(); formData.append("picture", file); //拼接请求体 xhr.send(formData); }