整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          手把手教你前端的各種文件上傳攻略和大文件斷點續傳



          在前面

          今年國慶假期終于可以憋在家里了不用出門了,不用出去看后腦了,真的是一種享受。這么好的光陰怎么浪費,睡覺、吃飯、打豆豆這怎么可能(耍多了也煩),完全不符合我們程序員的作風,趕緊起來把文章寫完。

          這篇文章比較基礎,在國慶期間的業余時間寫的,這幾天又完善了下,力求把更多的前端所涉及到的關于文件上傳的各種場景和應用都涵蓋了,若有疏漏和問題還請留言斧正和補充。

          自測讀不讀

          以下是本文所涉及到的知識點,break or continue ?

          • 文件上傳原理
          • 最原始的文件上傳
          • 使用 koa2 作為服務端寫一個文件上傳接口
          • 單文件上傳和上傳進度
          • 多文件上傳和上傳進度
          • 拖拽上傳
          • 剪貼板上傳
          • 大文件上傳之分片上傳
          • 大文件上傳之斷點續傳
          • node 端文件上傳

          原理概述

          原理很簡單,就是根據 http 協議的規范和定義,完成請求消息體的封裝和消息體的解析,然后將二進制內容保存到文件。

          我們都知道如果要上傳一個文件,需要把 form 標簽的enctype設置為multipart/form-data,同時method必須為post方法。

          那么multipart/form-data表示什么呢?

          multipart互聯網上的混合資源,就是資源由多種元素組成,form-data表示可以使用HTML Forms 和 POST 方法上傳文件,具體的定義可以參考RFC 7578。

          multipart/form-data 結構

          看下 http 請求的消息體



          • 請求頭:

          Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryDCntfiXcSkPhS4PN 表示本次請求要上傳文件,其中boundary表示分隔符,如果要上傳多個表單項,就要使用boundary分割,每個表單項由———XXX開始,以———XXX結尾。

          • 消息體- Form Data 部分

          每一個表單項又由Content-Type和Content-Disposition組成。

          Content-Disposition: form-data 為固定值,表示一個表單元素,name 表示表單元素的 名稱,回車換行后面就是name的值,如果是上傳文件就是文件的二進制內容。

          Content-Type:表示當前的內容的 MIME 類型,是圖片還是文本還是二進制數據。

          解析

          客戶端發送請求到服務器后,服務器會收到請求的消息體,然后對消息體進行解析,解析出哪是普通表單哪些是附件。

          可能大家馬上能想到通過正則或者字符串處理分割出內容,不過這樣是行不通的,二進制buffer轉化為string,對字符串進行截取后,其索引和字符串是不一致的,所以結果就不會正確,除非上傳的就是字符串。

          不過一般情況下不需要自行解析,目前已經有很成熟的三方庫可以使用。

          至于如何解析,這個也會占用很大篇幅,后面的文章在詳細說。

          最原始的文件上傳

          使用 form 表單上傳文件

          在 ie時代,如果實現一個無刷新的文件上傳那可是費老勁了,大部分都是用 iframe 來實現局部刷新或者使用 flash 插件來搞定,在那個時代 ie 就是最好用的瀏覽器(別無選擇)。

          DEMO



          這種方式上傳文件,不需要 js ,而且沒有兼容問題,所有瀏覽器都支持,就是體驗很差,導致頁面刷新,頁面其他數據丟失。

          HTML

           <form method="post" action="http://localhost:8100" enctype="multipart/form-data">
          
                  選擇文件:
                      <input type="file" name="f1"/> input 必須設置 name 屬性,否則數據無法發送<br/>
          <br/>
                      標題:<input type="text" name="title"/><br/><br/><br/>
          
                  <button type="submit" id="btn-0">上 傳</button>
          
          </form>
          
          復制代碼

          文件上傳接口

          服務端文件的保存基于現有的庫koa-body結合 koa2實現服務端文件的保存和數據的返回。

          在項目開發中,文件上傳本身和業務無關,代碼基本上都可通用。

          在這里我們使用koa-body庫來實現解析和文件的保存。

          koa-body 會自動保存文件到系統臨時目錄下,也可以指定保存的文件路徑。



          然后在后續中間件內得到已保存的文件的信息,再做二次處理。

          • ctx.request.files.f1 得到文件信息,f1為input file 標簽的 name
          • 獲得文件的擴展名,重命名文件

          NODE

          /**
           * 服務入口
           */
          var http = require('http');
          var koaStatic = require('koa-static');
          var path = require('path');
          var koaBody = require('koa-body');//文件保存庫
          var fs = require('fs');
          var Koa = require('koa2');
          
          var app = new Koa();
          var port = process.env.PORT || '8100';
          
          var uploadHost= `http://localhost:${port}/uploads/`;
          
          app.use(koaBody({
              formidable: {
                  //設置文件的默認保存目錄,不設置則保存在系統臨時目錄下  os
                  uploadDir: path.resolve(__dirname, '../static/uploads')
              },
              multipart: true // 開啟文件上傳,默認是關閉
          }));
          
          //開啟靜態文件訪問
          app.use(koaStatic(
              path.resolve(__dirname, '../static') 
          ));
          
          //文件二次處理,修改名稱
          app.use((ctx) => {
              var file = ctx.request.files.f1;//得道文件對象
              var path = file.path;
              var fname = file.name;//原文件名稱
              var nextPath = path+fname;
              if(file.size>0 && path){
                  //得到擴展名
                  var extArr = fname.split('.');
                  var ext = extArr[extArr.length-1];
                  var nextPath = path+'.'+ext;
                  //重命名文件
                  fs.renameSync(path, nextPath);
              }
              //以 json 形式輸出上傳文件地址
              ctx.body = `{
                  "fileUrl":"${uploadHost}${nextPath.slice(nextPath.lastIndexOf('/')+1)}"
              }`;
          });
          
          /**
           * http server
           */
          var server = http.createServer(app.callback());
          server.listen(port);
          console.log('demo1 server start ......   ');
          復制代碼

          CODE

          https://github.com/Bigerfe/fe-learn-code/

          了執行Javascript,需要在HTML文件內以特定的方式書寫JavaScript的代碼,JavaScript的書寫方法有多種,其執行的流程也各不相同:

          1 <script>標簽嵌入

          此種嵌入方法無法操作<script>之后的DOM元素。因為<script>之后的DOM元素還未構造,因此在<script>標簽內就無法取得位于其后的DOM元素。

          2 讀取外部JavaScript文件

          此種嵌入方法可以指定defer、async屬性。defer可以推遲執行,async可以異步執行。

          3 onload嵌入

          此種嵌入方法在頁面讀取完后再對其執行,所以可以對所有的DOM元素操作。

          <body onload="alert('hello')">
          window.onload = function(){alert('hello');};

          當window.onload事件觸發時,頁面上所有的DOM、樣式表、腳本、圖片、flash都已經加載完成了。

          //window.onload不能同時編寫多個。
          //以下代碼無法正確執行,結果只輸出第二個。
          window.onload = function(){
            alert("test1");
          };
          
          window.onload = function(){
            alert("test2");
          };
          
          //$(document).ready()能同時編寫多個
          //結果兩次都輸出
          $(document).ready(function(){ 
             alert("Hello World"); 
          }); 
          $(document).ready(function(){ 
             alert("Hello again"); 
          }); 

          window.onload和body中onload也有些許區別:

          <html xmlns="http://www.w3.org/1999/xhtml">
          <head>
              <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
              <title></title>
              <script type="text/javascript" src="http://code.jquery.com/jquery-1.9.0.js"></script>
              <script language="javascript">
                  window.onload = haha;
                  function haha(){console.log("window.onload");}
          
                  if(document.addEventListener){
                      function DOMContentLoaded(){
                          console.log("DOMContentLoaded");
                      }
                      document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
                  }</script>
          </head>
          <body onload="console.log('bodyonload');">
                  <div id="div1">a</div>
          </body>
          </html>

          在IE10和FireFox下,結果為 :

          "DOMContentLoaded"
          "bodyonload"

          說明body中的onload會覆蓋window.onload

          在chrome下,結果為:

          DOMContentLoaded
          window.onload
          bodyonload

          然后,如果把javascript代碼移到最下面,結果又會是什么樣呢?

          chrome和IE10、FireFox的結果竟然是一樣的:

          DOMContentLoaded
          window.onload

          IE 10、Fire Fox可以理解,window.on load和body中的 on load 誰在下面就是誰覆蓋誰,只會執行后面的那個。

          4 DOM ContentLoaded嵌入

          onload方法可能需要等待時間,而本方法可以在完成HTML解析后發生的事件,減少等待時間。

          在chrome、IE10和FireFox中,執行結果是:DOMContentLoaded然后才是onload的輸出。所以說一般情況下,DOMContentLoaded事件要在window.onload之前執行,當DOM樹構建完成的時候就會執行DOMContentLoaded事件。

          <html xmlns="http://www.w3.org/1999/xhtml">
          <head>
              <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
              <title></title>
              <script type="text/javascript" src="jquery2.js"></script>
              <script language="javascript">
                  window.onload = haha;
                  function haha(){console.log(document.getElementById("div1"));}
                  if(document.addEventListener){
                      function DOMContentLoaded(){
                          console.log("DOMContentLoaded");
                      }
                      document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
                  }
              </script>
          </head>
          <body>
              <div id="div1">a</div>
          </body>
          </html>

          如果你是個jQuery使用者,你可能會經常使用$(document).ready();或者$(function(){}),這都是使用了DOMContentLoaded事件

          5 動態載入JavaScript文件

          5.1 使用原生js方法

          動態創建script標簽,并指定script的src屬性

          function loadJs(url, callback) {
              var script = document.createElement('script');
              script.type = "text/javascript";
              if (typeof(callback) != "undefined") {
                  if (script.readyState) {
                      script.onreadystatechange = function() {
                          if (script.readyState == "loaded" || script.readyState == "complete") {
                              script.onreadystatechange = null;
                              callback();
                          }
                      }
                  } else {
                      script.onload = function() {
                          callback();
                      }
                  }
              }
              script.src = url;
              document.body.appendChild(script);
          }
          loadJs("test.js", function() {
              alert('done');
          });

          還可以使用同樣的原理動態加載css文件,只不過插入的的父節點是head標簽。

          5.2 使用document.write/writeln()方式

          該種方式可以實現js文件的動態加載,原理就是在重寫文檔流,這種方式會導致整個頁面重繪。

          document.writeln("<script src=\"http://lib.sinaapp.com/js/jquery/1.6/jquery.min.js\"></script>");

          需要注意的是特殊字符的轉義。

          5.3 使用jQuery

          使用getScript(url,callback)方法實現動態加載js文件

          $.getScript('test.js',function(){
              alert('done');
          });

          -End-

          ello~各位親愛的看官老爺們大家好。估計大家都聽過,盡量將 CSS 放頭部, JS 放底部,這樣可以提高頁面的性能。然而,為什么呢?大家有考慮過么?很長一段時間,我都是知其然而不知其所以然,強行背下來應付考核當然可以,但實際應用中必然一塌糊涂。因此洗(wang)心(yang)革(bu)面(lao),小結一下最近玩出來的成果。

          友情提示,本文也是小白向為主,如果直接想看結論可以拉到最下面看的~

          推薦下我的前端群:524262608,不定期分享干貨,包括我自己整理的一份2017最新的前端資料和零基礎入門教程,歡迎初學和進階中的小伙伴。

          由于關系到文件的讀取,那是肯定需要服務器的,我會把全部的文件放在 github 上,給我點個 star 我會開心!掘金上再給我點個 贊 我就更開心了~

          node 端唯一需要解釋一下的是這個函數:

          function sleep(time) { return new Promise(function(res) {
          setTimeout(() => {
          res()
          }, time);
          })
          }

          嗯!其實就延時啦。如果 CSS 或者 JS 文件名有 sleep3000 之類的前綴時,意思就是延遲3000毫秒才會返回這文件。

          下文使用的 HTML 文件是長這樣的:

          <!DOCTYPE html>
          <html lang="en">
          <head>
          <meta charset="UTF-8">
          <title>Title</title>
          <style>
           div { width: 100px; height: 100px; background: lightgreen;
           } </style>
          </head>
          <body>
          <div></div>
          </body>
          </html>

          我會在其中插入不同的 JS 和 CSS 。

          而使用的 common.css ,不論有沒有前綴,內容都是這樣的:

          div { background: red;
          }

          好了,話不多數,開始正文!

          關于 CSS ,大家肯定都知道的是 <link> 標簽放在頭部性能會高一點,少一點人知道如果 <script> 與 <link> 同時在頭部的話, <script> 在上可能會更好。這是為什么呢?下面我們一起來看一下 CSS 對 DOM 的影響是什么。

          CSS 不會阻塞 DOM 的解析

          注意哦!這里說的是 DOM 解析,證明的例子如下,首先在頭部插入 <script defer src="/js/logDiv.js"></script> , JS 文件的內容是:

          const div = document.querySelecot('div');
          console.log(div);

          defer 屬性相信大家也很熟悉了,MDN對此的描述是用來通知瀏覽器該腳本將在文檔完成解析后,觸發 DOMContentLoaded 事件前執行。設置這個屬性,能保證 DOM 解析后馬上打印出 div 。

          之后將 <link rel="stylesheet" href="/css/sleep3000-common.css"> 插入 HTML 文件的任一位置,打開瀏覽器,可以看到是首先打印出 div 這個 DOM 節點,過3s左右之后才渲染出一個淺藍色的 div 。這就證明了 CSS 是不會阻塞 DOM 的解析的,盡管 CSS 下載需要3s,但這個過程中,瀏覽器不會傻等著 CSS 下載完,而是會解析 DOM 的。

          這里簡單說一下,瀏覽器是解析 DOM 生成 DOM Tree ,結合 CSS 生成的 CSS Tree ,最終組成 render tree ,再渲染頁面。由此可見,在此過程中 CSS 完全無法影響 DOM Tree ,因而無需阻塞 DOM 解析。然而, DOM Tree 和 CSS Tree 會組合成 render tree ,那 CSS 會不會頁面阻塞渲染呢?

          CSS 阻塞頁面渲染

          其實這一點,剛才的例子已經說明了,如果 CSS 不會阻塞頁面阻塞渲染,那么 CSS 文件下載之前,瀏覽器就會渲染出一個淺綠色的 div ,之后再變成淺藍色。瀏覽器的這個策略其實很明智的,想象一下,如果沒有這個策略,頁面首先會呈現出一個原始的模樣,待 CSS 下載完之后又突然變了一個模樣。用戶體驗可謂極差,而且渲染是有成本的。

          因此,基于性能與用戶體驗的考慮,瀏覽器會盡量減少渲染的次數, CSS 順理成章地阻塞頁面渲染。

          然而,事情總有奇怪的,請看這例子, HTML 頭部結構如下:

          <header>
          <link rel="stylesheet" href="/css/sleep3000-common.css">
          <script src="/js/logDiv.js"></script></header>

          但思考一下這會產生什么結果呢?

          答案是瀏覽器會轉圈圈三秒,但此過程中不會打印任何東西,之后呈現出一個淺藍色的 div ,再打印出 null 。結果好像是 CSS 不單阻塞了頁面渲染,還阻塞了 DOM 的解析啊!稍等,在你打算掀桌子瘋狂吐槽我之前,請先思考一下是什么阻塞了 DOM 的解析,剛才已經證明了 CSS 是不會阻塞的,那么阻塞了頁面解析其實是 JS !但明明 JS 的代碼如此簡單,肯定不會阻塞這么久,那就是 JS 在等待 CSS 的下載,這是為什么呢?

          仔細思考一下,其實這樣做是有道理的,如果腳本的內容是獲取元素的樣式,寬高等 CSS 控制的屬性,瀏覽器是需要計算的,也就是依賴于 CSS 。瀏覽器也無法感知腳本內容到底是什么,為避免樣式獲取,因而只好等前面所有的樣式下載完后,再執行 JS 。因而造成了之前例子的情況。

          所以,看官大人明白為何 <script> 與 <link> 同時在頭部的話, <script> 在上可能會更好了么?之所以是可能,是因為如果 <link> 的內容下載更快的話,是沒影響的,但反過來的話, JS 就要等待了,然而這些等待的時間是完全不必要的。

          JS

          JS ,也就是 <script> 標簽,估計大家都很熟悉了,不就是阻塞 DOM 解析和渲染么。然而,其中其實還是有一點細節可以考究一下的,我們一起來好好看看。

          JS 阻塞 DOM 解析

          首先我們需要一個新的 JS 文件名為 blok.js ,內容如下:

          <body>
          <div></div>
          <script src="/js/sleep3000-logDiv.js"></script>
          <style>
           div { background: lightgrey;
           } </style>
          <script src="/js/sleep5000-logDiv.js"></script>
          <link rel="stylesheet" href="/css/common.css"></body>

          這個例子也是很極端的例子,但不妨礙它透露給我們很多重要的信息。想象一下,頁面會怎樣呢?

          答案是先淺綠色,再淺灰色,最后淺藍色。由此可見,每次碰到 <script> 標簽時,瀏覽器都會渲染一次頁面。這是基于同樣的理由,瀏覽器不知道腳本的內容,因而碰到腳本時,只好先渲染頁面,確保腳本能獲取到最新的 DOM 元素信息,盡管腳本可能不需要這些信息。

          小結

          綜上所述,我們得出這樣的結論:

          CSS 不會阻塞 DOM 的解析,但會阻塞 DOM 渲染。

          JS 阻塞 DOM 解析,但瀏覽器會"偷看" DOM ,預先下載相關資源。

          最后還是要推薦下我的前端群:524262608,不定期分享干貨,包括我自己整理的一份2017最新的前端資料和零基礎入門教程,歡迎初學和進階中的小伙伴。

          瀏覽器遇到 <script> 且沒有 defer 或 async 屬性的 標簽時,會觸發頁面渲染,因而如果前面 CSS 資源尚未加載完畢時,瀏覽器會等待它加載完畢在執行腳本。

          所以,你現在明白為何 <script> 最好放底部, <link> 最好放頭部,如果頭部同時有 <script> 與 <link> 的情況下,最好將 <script> 放在 <link> 上面了嗎?

          web前端學習方法經驗可以關注我的微信公眾號:‘學習web前端’,每天更新案例源碼或經驗分享,關注后回復‘給我資料’可以領取一套完整的學習視頻


          主站蜘蛛池模板: 无码人妻精品一区二区三区99仓本| 日韩AV片无码一区二区不卡| 久久久人妻精品无码一区| 在线电影一区二区| 精品久久久久久中文字幕一区| 日本一区二区三区在线看| 一区二区三区在线免费观看视频 | 色欲AV蜜桃一区二区三| 天海翼一区二区三区高清视频| 人妻少妇精品视频三区二区一区 | 91香蕉福利一区二区三区| 精品国产一区二区三区无码| 亚洲AV无码一区二区乱子仑| 一级毛片完整版免费播放一区| 日亚毛片免费乱码不卡一区| 一区三区三区不卡| 久久精品岛国av一区二区无码| 在线精品视频一区二区| 精品中文字幕一区二区三区四区| 国产无套精品一区二区| 亚洲日本乱码一区二区在线二产线| 亚洲国产欧美一区二区三区| 无码毛片一区二区三区中文字幕| 国产无套精品一区二区| 精品一区二区ww| 国产亚洲一区二区三区在线| 无码国产精品一区二区免费虚拟VR | 国产精品区一区二区三在线播放| 蜜臀AV在线播放一区二区三区| 相泽南亚洲一区二区在线播放| 手机看片福利一区二区三区| 在线欧美精品一区二区三区 | 在线播放一区二区| 波多野结衣中文字幕一区| 一区二区三区日韩精品| 国产美女av在线一区| 中文字幕日韩欧美一区二区三区| 国产波霸爆乳一区二区| 国产精品伦子一区二区三区| 麻豆一区二区99久久久久| 亚洲Aⅴ无码一区二区二三区软件|