基础-XHR和Fetch

前面的http范围是比较广的,而前端工程师大部分是要和浏览器打交道,所以主要研究浏览器的网络通信能力。

1. 用户代理

浏览器可以代替用户完成http请求,代替用户解析响应结果,所以我们称之为用户代理user agent。 在网络层面,对于前端开发者,必须要知道的浏览器两大核心能力:自动发出请求的能力自动解析响应的能力

1.自动发出请求的能力

当一些事情发生的时候,浏览器会代替用户自动发出http请求,常见的包括:

  1. 用户在地址栏输入了一个url地址,并按下了回车。

    浏览器会自动解析url,并发送一个GET请求,同时抛弃当前页面。

  2. 当用户点击了页面中的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。同理,../表示当前路径的倒数第二个斜杠。
  3. 当用户点击了提交按钮<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>
  4. 当解析html时遇到<link> <script> <img> <video> <audio>等元素

    浏览器会拿到对应的地址,发出GET请求。

  5. 当用户点击了刷新

    浏览器会拿到当前页面的地址,以及当前页面的请求方法,重新发送一次请求,同时抛弃当前页面。

    PS:浏览器和服务器有着一个约定:当发送GET请求时,不会附带请求体。

    这个约定深刻地影响着后续的前后端各种应用。

    1.浏览器在发送GET请求时,不会附带请求体;

    2.GET请求的传递信息量有限,适合传递少量数据;POST请求的传递信息量是没有限制的,适合传输大量数据。

    3.GET请求只能传递ASCII数据,遇到非ASCII数据需要进行编码;POST请求没有限制。

    4.大部分GET请求传递的数据都附带在path参数中,能够通过分享地址完整的重现页面,但同时也暴露了数据,若有敏感数据传递,不应该使用GET请求,至少不应该放到path中。

    5.POST不会被保存到浏览器的历史记录中。

    6.刷新页面时,若当前的页面是通过POST请求得到的,则浏览器会提示用是否重新提交。若是GET请求得到的页面则没有提示。

2.自动解析响应的能力

浏览器不仅能发送请求,还能够针对服务器的各种响应结果做出不同的自动处理。

  1. 识别响应码

    浏览器能够自动识别响应码,当出现一些特殊的响应码时,浏览器会自动完成处理,比如301、302

  2. 根据响应结果做不同的处理

    浏览器能够自动分析响应头中的Content-Type,根据不同的值进行不同的处理,比如:

    • text/plain:普通的纯文本,浏览器通常会将响应体原封不动的显示在页面上。
    • text/html:html文档,浏览器通常会将响应体作为页面进行渲染。
    • text/javascriptapplication/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应用程序中异步向服务器发送请求。它的实现方式有两种,XMLHttpRequestfetch

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

  1. 准备好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>

    有三个类selectprogressresult控制三个部分的显示与隐藏。

  1. 设置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);
    }
  2. 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];  
       };
  3. 上传文件

    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,方便拼接数据。

    js 复制代码
    function 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);
    }

    现在图片已经是能上传成功了,然后我们需要监听图片的响应完成与进度。

    js 复制代码
    function 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);
    }
相关推荐
谈谈叭2 分钟前
Javascript中的深浅拷贝以及实现方法
开发语言·javascript·ecmascript
优雅永不过时·25 分钟前
Three.js 原生 实现 react-three-fiber drei 的 磨砂反射的效果
前端·javascript·react.js·webgl·threejs·three
爱编程的鱼1 小时前
javascript用来干嘛的?赋予网站灵魂的语言
开发语言·javascript·ecmascript
神夜大侠3 小时前
VUE 实现公告无缝循环滚动
前端·javascript·vue.js
明辉光焱3 小时前
【Electron】Electron Forge如何支持Element plus?
前端·javascript·vue.js·electron·node.js
柯南二号3 小时前
HarmonyOS ArkTS 下拉列表组件
前端·javascript·数据库·harmonyos·arkts
wyy72933 小时前
v-html 富文本中图片使用element-ui image-viewer组件实现预览,并且阻止滚动条
前端·ui·html
前端郭德纲4 小时前
ES6的Iterator 和 for...of 循环
前端·ecmascript·es6
究极无敌暴龙战神X4 小时前
前端学习之ES6+
开发语言·javascript·ecmascript
王解4 小时前
【模块化大作战】Webpack如何搞定CommonJS与ES6混战(3)
前端·webpack·es6