整合營銷服務商

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

          免費咨詢熱線:

          FileReader實現圖片預覽

          FileReader實現圖片預覽

          .檢測瀏覽器對FileReader兼容性的方法:

          if(window.FileReader) {
          var fr=new FileReader();
          // add your code here
          }
          else {
          alert("Not supported by your browser!");
          }

          方法二:檢測FileReader類型

          if(typeof FileReader==='undefined'){
          alert('您的瀏覽器不支持圖片上傳,請升級您的瀏覽器');
          return false;
          }

          2.調用fileReader對象的方法

          FileReader實例擁有四個方法, 其中三個是用來讀取文件, 另一個是用來中斷讀取的。需要注意的是, 無論讀取成功或是失敗, 方法并不會返回讀取結果,

          這一結果(儲存在result屬性中)要用FileReader處理事件去獲取;

          方法名 參數 描述

          abort none 中斷讀取

          readAsBinaryString file 將文件轉化為二進制碼

          readAsDataURL file 讀取文件內容, 結果用data:url的字符串形式表示

          readAsText file,[encoding] 將文件讀取為文本

          readAsText:該方法有兩個參數, 其中第二個參數是文本的編碼方式, 默認值為 UTF-8。這個方法非常容易理解, 將文件以文本方式讀取, 讀取的結果即是這個文本文件中的內容。

          readAsText(file,encoding)可按指定編碼方式讀取文件, 但讀取文件的單位是字符, 故對于文本文件, 只要按規定的編碼方式讀取即可; 而對于媒體文件(圖片、音頻、視頻),

          其內部組成并不是按字符排列, 故采用readAsText讀取, 會產生亂碼, 因此不是最理想的讀取文件的方式。

          readAsBinaryString:該方法將文件讀取為二進制字符串, 通常我們將它傳送到后端, 后端可以通過這段字符串存儲文件。

          readAsDataURL:這是例子程序中用到的方法, 該方法將文件讀取為一段以 data: 開頭的字符串, 這段字符串的實質就是 Data URL, Data URL是一種將小文件直接嵌入文檔的方案。

          這里的小文件通常是指圖像與 html 等格式的文件。控制臺為當前所傳文件的base64編碼表示。

          由于媒體文件的src屬性, 可以通過采用網絡地址或base64的方式顯示,因此我們可以利用readAsDataURL實現對圖片的預覽。

          3.處理事件

          FileReader 包含了一整套完成的事件模型,用于捕獲讀取文件時的狀態,下面這個表格歸納了這些事件。

          事件 描述

          onabort 中斷時觸發

          onerror 出錯時觸發

          onload 文件讀取成功完成時觸發

          onloadend 讀取完成時觸發,無論讀取成功或失敗

          onloadstart 讀取開始時觸發

          onprogress 讀取中

          result 返回文件的內容。只有在讀取操作完成后,此屬性才有效,返回的數據的格式取決于是使用哪種讀取方法來執行讀取操作的。

          readyState 提供 FileReader 讀取操作時的當前狀態。

          家好,我是 Echa。

          好久沒跟粉絲們細聊JavaScript那點事了。做一名全棧工程師,JS基礎還是要打牢,這樣的話不管底層業務邏輯以及第三方框架怎么變化,都離不開基礎。本文文章屬于基礎篇,閱讀有點乏味枯燥,但一定能學到知識。創作不易,喜歡的老鐵們加個關注,點個贊,后面會持續更新干貨,速速收藏,謝謝!

          JavaScript 提供了一些 API 來處理文件或原始文件數據,例如:File、Blob、FileReader、ArrayBuffer、base64 等。下面就來看看它們都是如何使用的,它們之間又有何區別和聯系!

          上面的這個圖是在線工具畫的,打開就可以用的網址如下:

          https://excalidraw.com/

          1. Blob

          Blob 全稱為 binary large object ,即二進制大對象,它是 JavaScript 中的一個對象,表示原始的類似文件的數據。下面是 MDN 中對 Blob 的解釋:

          Blob 對象表示一個不可變、原始數據的類文件對象。它的數據可以按文本或二進制的格式進行讀取,也可以轉換成 ReadableStream 來用于數據操作。

          實際上,Blob 對象是包含有只讀原始數據的類文件對象。簡單來說,Blob 對象就是一個不可修改的二進制文件。

          (1)Blob 創建

          可以使用 Blob() 構造函數來創建一個 Blob:

          new Blob(array, options);
          

          其有兩個參數:

          • array:由 ArrayBufferArrayBufferViewBlobDOMString 等對象構成的,將會被放進 Blob
          • options:可選的 BlobPropertyBag 字典,它可能會指定如下兩個屬性
            • type:默認值為 "",表示將會被放入到 blob 中的數組內容的 MIME 類型。
            • endings:默認值為"transparent",用于指定包含行結束符\n的字符串如何被寫入,不常用。

          常見的 MIME 類型如下:

          下面來看一個簡單的例子:

          const blob=new Blob(["Hello World"], {type: "text/plain"});
          

          這里可以成為動態文件創建,其正在創建一個類似文件的對象。這個 blob 對象上有兩個屬性:

          • size:Blob對象中所包含數據的大小(字節);
          • type:字符串,認為該Blob對象所包含的 MIME 類型。如果類型未知,則為空字符串。

          下面來看打印結果:

          const blob=new Blob(["Hello World"], {type: "text/plain"});
          
          console.log(blob.size); // 11
          console.log(blob.type); // "text/plain"
          

          注意,字符串"Hello World"是 UTF-8 編碼的,因此它的每個字符占用 1 個字節。

          到現在,Blob 對象看起來似乎我們還是沒有啥用。那該如何使用 Blob 對象呢?可以使用 URL.createObjectURL() 方法將將其轉化為一個 URL,并在 Iframe 中加載:

          <iframe></iframe>
          
          const iframe=document.getElementsByTagName("iframe")[0];
          
          const blob=new Blob(["Hello World"], {type: "text/plain"});
          
          iframe.src=URL.createObjectURL(blob);
          

          (2)Blob 分片

          除了使用Blob()構造函數來創建blob 對象之外,還可以從 blob 對象中創建blob,也就是將 blob 對象切片。Blob 對象內置了 slice() 方法用來將 blob 對象分片,其語法如下:

          const blob=instanceOfBlob.slice([start [, end [, contentType]]]};
          

          其有三個參數:

          • start:設置切片的起點,即切片開始位置。默認值為 0,這意味著切片應該從第一個字節開始;
          • end:設置切片的結束點,會對該位置之前的數據進行切片。默認值為blob.size
          • contentType:設置新 blob 的 MIME 類型。如果省略 type,則默認為 blob 的原始值。

          下面來看例子:

          const iframe=document.getElementsByTagName("iframe")[0];
          
          const blob=new Blob(["Hello World"], {type: "text/plain"});
          
          const subBlob=blob.slice(0, 5);
          
          iframe.src=URL.createObjectURL(subBlob);
          

          此時頁面會顯示"Hello"。

          2. File

          文件(File)接口提供有關文件的信息,并允許網頁中的 JavaScript 訪問其內容。實際上,File 對象是特殊類型的 Blob,且可以用在任意的 Blob 類型的 context 中。Blob 的屬性和方法都可以用于 File 對象。

          注意:File 對象中只存在于瀏覽器環境中,在 Node.js 環境中不存在。

          在 JavaScript 中,主要有兩種方法來獲取 File 對象:

          • <input> 元素上選擇文件后返回的 FileList 對象;
          • 文件拖放操作生成的 DataTransfer 對象;

          (1)input

          首先定義一個輸入類型為 file 的 input 標簽:

          <input type="file" id="fileInput" multiple="multiple">
          

          這里給 input 標簽添加了三個屬性:

          • type="file":指定 input 的輸入類型為文件;
          • id="fileInput":指定 input 的唯一 id;
          • multiple="multiple":指定 input 可以同時上傳多個文件;

          下面來給 input 標簽添加 onchange 事件,當選擇文件并上傳之后觸發:

          const fileInput=document.getElementById("fileInput");
          
          fileInput.onchange=(e)=> {
              console.log(e.target.files);
          }
          

          當點擊上傳文件時,控制臺就會輸出一個 FileList 數組,這個數組的每個元素都是一個 File 對象,一個上傳的文件就對應一個 File 對象:

          每個 File 對象都包含文件的一些屬性,這些屬性都繼承自 Blob 對象:

          • lastModified:引用文件最后修改日期,為自1970年1月1日0:00以來的毫秒數;
          • lastModifiedDate:引用文件的最后修改日期;
          • name:引用文件的文件名;
          • size:引用文件的文件大小;
          • type:文件的媒體類型(MIME);
          • webkitRelativePath:文件的路徑或 URL。

          通常,我們在上傳文件時,可以通過對比 size 屬性來限制文件大小,通過對比 type 來限制上傳文件的格式等。

          (2)文件拖放

          另一種獲取 File 對象的方式就是拖放 API,這個 API 很簡單,就是將瀏覽器之外的文件拖到瀏覽器窗口中,并將它放在一個成為拖放區域的特殊區域中。拖放區域用于響應放置操作并從放置的項目中提取信息。這些是通過 ondropondragover 兩個 API 實現的。

          下面來看一個簡單的例子,首先定義一個拖放區域:

          <div id="drop-zone"></div>
          

          然后給這個元素添加 ondragoverondrop 事件處理程序:

          const dropZone=document.getElementById("drop-zone");
          
          dropZone.ondragover=(e)=> {
              e.preventDefault();
          }
          
          dropZone.ondrop=(e)=> {
              e.preventDefault();
              const files=e.dataTransfer.files;
              console.log(files)
          }
          

          注意:這里給兩個 API 都添加了 e.preventDefault(),用來阻止默認事件。它是非常重要的,可以用來阻止瀏覽器的一些默認行為,比如放置文件將顯示在瀏覽器窗口中。

          當拖放文件到拖放區域時,控制臺就會輸出一個 FileList 數組,該數組的每一個元素都是一個 File 對象。這個 FileList 數組是從事件參數的 dataTransfer 屬性的 files 獲取的:

          可以看到,這里得到的 File 對象和通過 input 標簽獲得的 File 對象是完全一樣的。

          3. FileReader

          FileReader 是一個異步 API,用于讀取文件并提取其內容以供進一步使用。FileReader 可以將 Blob 讀取為不同的格式。

          注意:FileReader 僅用于以安全的方式從用戶(遠程)系統讀取文件內容,不能用于從文件系統中按路徑名簡單地讀取文件。

          (1)基本使用

          可以使用 FileReader 構造函數來創建一個 FileReader 對象:

          const reader=new FileReader();
          

          這個對象常用屬性如下:

          • error:表示在讀取文件時發生的錯誤;
          • result:文件內容。該屬性僅在讀取操作完成后才有效,數據的格式取決于使用哪個方法來啟動讀取操作。
          • readyState:表示FileReader狀態的數字。取值如下:

          FileReader 對象提供了以下方法來加載文件:

          • readAsArrayBuffer():讀取指定 Blob 中的內容,完成之后,result 屬性中保存的將是被讀取文件的 ArrayBuffer 數據對象;
          • FileReader.readAsBinaryString():讀取指定 Blob 中的內容,完成之后,result 屬性中將包含所讀取文件的原始二進制數據;
          • FileReader.readAsDataURL():讀取指定 Blob 中的內容,完成之后,result 屬性中將包含一個data: URL 格式的 Base64 字符串以表示所讀取文件的內容。
          • FileReader.readAsText():讀取指定 Blob 中的內容,完成之后,result 屬性中將包含一個字符串以表示所讀取的文件內容。

          可以看到,上面這些方法都接受一個要讀取的 blob 對象作為參數,讀取完之后會將讀取的結果放入對象的 result 屬性中。

          (2)事件處理

          FileReader 對象常用的事件如下:

          • abort:該事件在讀取操作被中斷時觸發;
          • error:該事件在讀取操作發生錯誤時觸發;
          • load:該事件在讀取操作完成時觸發;
          • progress:該事件在讀取 Blob 時觸發。

          當然,這些方法可以加上前置 on 后在HTML元素上使用,比如onloadonerroronabortonprogress。除此之外,由于FileReader對象繼承自EventTarget,因此還可以使用 addEventListener() 監聽上述事件。

          下面來看一個簡單的例子,首先定義一個 input 輸入框用于上傳文件:

          <input type="file" id="fileInput">
          

          接下來定義 input 標簽的 onchange 事件處理函數和FileReader對象的onload事件處理函數:

          const fileInput=document.getElementById("fileInput");
          
          const reader=new FileReader();
          
          fileInput.onchange=(e)=> {
              reader.readAsText(e.target.files[0]);
          }
          
          reader.onload=(e)=> {
              console.log(e.target.result);
          }
          

          這里,首先創建了一個 FileReader 對象,當文件上傳成功時,使用 readAsText() 方法讀取 File 對象,當讀取操作完成時打印讀取結果。

          使用上述例子讀取文本文件時,就是比較正常的。如果讀取二進制文件,比如png格式的圖片,往往會產生亂碼,如下:

          那該如何處理這種二進制數據呢?readAsDataURL() 是一個不錯的選擇,它可以將讀取的文件的內容轉換為 base64 數據的 URL 表示。這樣,就可以直接將 URL 用在需要源鏈接的地方,比如 img 標簽的 src 屬性。

          對于上述例子,將 readAsText 方法改為 readAsDataURL()

          const fileInput=document.getElementById("fileInput");
          
          const reader=new FileReader();
          
          fileInput.onchange=(e)=> {
              reader.readAsDataURL(e.target.files[0]);
          }
          
          reader.onload=(e)=> {
              console.log(e.target.result);
          }
          

          這時,再次上傳二進制圖片時,就會在控制臺打印一個 base64 編碼的 URL,如下:

          下面來修改一下這個例子,將上傳的圖片通過以上方式顯示在頁面上:

          <input type="file" id="fileInput" />
          
          <img id="preview" />
          
          const fileInput=document.getElementById("fileInput");
          const preview=document.getElementById("preview");
          const reader=new FileReader();
          
          fileInput.onchange=(e)=> {
            reader.readAsDataURL(e.target.files[0]);
          };
          
          reader.onload=(e)=> {
            preview.src=e.target.result;
            console.log(e.target.result);
          };
          

          當上傳大文件時,可以通過 progress 事件來監控文件的讀取進度:

          const reader=new FileReader();
          
          reader.onprogress=(e)=> {
            if (e.loaded && e.total) {
              const percent=(event.loaded / event.total) * 100;
              console.log(`上傳進度: ${Math.round(percent)} %`);
            }
          });
          

          progress 事件提供了兩個屬性:loaded(已讀取量)和total(需讀取總量)。

          4. ArrayBuffer

          (1)ArrayBuffer

          ArrayBuffer 對象用來表示通用的、固定長度的原始二進制數據緩沖區。ArrayBuffer 的內容不能直接操作,只能通過 DataView 對象或 TypedArrray 對象來訪問。這些對象用于讀取和寫入緩沖區內容。

          ArrayBuffer 本身就是一個黑盒,不能直接讀寫所存儲的數據,需要借助以下視圖對象來讀寫:

          • TypedArray:用來生成內存的視圖,通過9個構造函數,可以生成9種數據格式的視圖。
          • DataViews:用來生成內存的視圖,可以自定義格式和字節序。

          TypedArray視圖和 DataView視圖的區別主要是字節序,前者的數組成員都是同一個數據類型,后者的數組成員可以是不同的數據類型。

          那 ArrayBuffer 與 Blob 有啥區別呢?根據 ArrayBuffer 和 Blob 的特性,Blob 作為一個整體文件,適合用于傳輸;當需要對二進制數據進行操作時(比如要修改某一段數據時),就可以使用 ArrayBuffer。

          下面來看看 ArrayBuffer 有哪些常用的方法和屬性。

          ① new ArrayBuffer()

          ArrayBuffer 可以通過以下方式生成:

          new ArrayBuffer(bytelength)
          

          ArrayBuffer()構造函數可以分配指定字節數量的緩沖區,其參數和返回值如下:

          • 參數:它接受一個參數,即 bytelength,表示要創建數組緩沖區的大小(以字節為單位。);
          • 返回值:返回一個新的指定大小的ArrayBuffer對象,內容初始化為0。

          ② ArrayBuffer.prototype.byteLength

          ArrayBuffer 實例上有一個 byteLength 屬性,它是一個只讀屬性,表示 ArrayBuffer 的 byte 的大小,在 ArrayBuffer 構造完成時生成,不可改變。來看例子:

          const buffer=new ArrayBuffer(16); 
          console.log(buffer.byteLength);  // 16
          

          ③ ArrayBuffer.prototype.slice()

          ArrayBuffer 實例上還有一個 slice 方法,該方法可以用來截取 ArrayBuffer 實例,它返回一個新的 ArrayBuffer ,它的內容是這個 ArrayBuffer 的字節副本,從 begin(包括),到 end(不包括)。來看例子:

          const buffer=new ArrayBuffer(16); 
          console.log(buffer.slice(0, 8));  // 16
          

          這里會從 buffer 對象上將前8個字節生成一個新的ArrayBuffer對象。這個方法實際上有兩步操作,首先會分配一段指定長度的內存,然后拷貝原來ArrayBuffer對象的置頂部分。

          ④ ArrayBuffer.isView()

          ArrayBuffer 上有一個 isView()方法,它的返回值是一個布爾值,如果參數是 ArrayBuffer 的視圖實例則返回 true,例如類型數組對象或 DataView 對象;否則返回 false。簡單來說,這個方法就是用來判斷參數是否是 TypedArray 實例或者 DataView 實例:

          const buffer=new ArrayBuffer(16);
          ArrayBuffer.isView(buffer)   // false
          
          const view=new Uint32Array(buffer);
          ArrayBuffer.isView(view)     // true
          

          (2)TypedArray

          TypedArray 對象一共提供 9 種類型的視圖,每一種視圖都是一種構造函數。如下:

          元素類型化數組字節描述Int8Int8Array18 位有符號整數Uint8Uint8Array18 位無符號整數Uint8CUint8ClampedArray18 位無符號整數Int16Int16Array216 位有符號整數Uint16Uint16Array216 位無符號整數Int32Int32Array432 位有符號整數Uint32Uint32Array432 位無符號整數Float32Float32Array432 位浮點Float64Float64Array864 位浮點

          來看看這些都是什么意思:

          • Uint8Array: 將 ArrayBuffer 中的每個字節視為一個整數,可能的值從 0 到 255 (一個字節等于 8 位)。 這樣的值稱為“8 位無符號整數”。
          • Uint16Array:將 ArrayBuffer 中任意兩個字節視為一個整數,可能的值從 0 到 65535。 這樣的值稱為“16 位無符號整數”。
          • Uint32Array:將 ArrayBuffer 中任何四個字節視為一個整數,可能值從 0 到 4294967295,這樣的值稱為“32 位無符號整數”。

          這些構造函數生成的對象統稱為 TypedArray 對象。它們和正常的數組很類似,都有length屬性,都能用索引獲取數組元素,所有數組的方法都可以在類型化數組上面使用。

          那類型化數組和數組有什么區別呢?

          • 類型化數組的元素都是連續的,不會為空;
          • 類型化數組的所有成員的類型和格式相同;
          • 類型化數組元素默認值為 0;
          • 類型化數組本質上只是一個視圖層,不會存儲數據,數據都存儲在更底層的 ArrayBuffer 對象中。

          下面來看看 TypedArray 都有哪些常用的方法和屬性。

          ① new TypedArray()

          TypedArray 的語法如下(TypedArray只是一個概念,實際使用的是那9個對象):

          new Int8Array(length);
          new Int8Array(typedArray);
          new Int8Array(object);
          new Int8Array(buffer [, byteOffset [, length]]);
          

          可以看到,TypedArray 有多種用法,下面來分別看一下。

          • TypedArray(length) :通過分配指定長度內容進行分配
          let view=new Int8Array(16);
          view[0]=10;
          view[10]=6;
          console.log(view);
          

          輸出結果如下:

          這里就生成了一個 16個元素的 Int8Array 數組,除了手動賦值的元素,其他元素的初始值都是 0。

          • TypedArray(typeArray) :接收一個視圖實例作為參數
          const view=new Int8Array(new Uint8Array(6));
          view[0]=10;
          view[3]=6;
          console.log(view);
          

          輸出結果如下:

          • TypedArray(object) :參數可以是一個普通數組
          const view=new Int8Array([1, 2, 3, 4, 5]);
          view[0]=10;
          view[3]=6;
          console.log(view);
          

          輸出結果如下:

          需要注意,TypedArray視圖會開辟一段新的內存,不會在原數組上建立內存。當然,這里創建的類型化數組也能轉換回普通數組:

          Array.prototype.slice.call(view); // [10, 2, 3, 6, 5]
          
          • TypeArray(buffer [, byteOffset [, length]])

          這種方式有三個參數,其中第一個參數是一個ArrayBuffer對象;第二個參數是視圖開始的字節序號,默認從0開始,可選;第三個參數是視圖包含的數據個數,默認直到本段內存區域結束。

          const buffer=new ArrayBuffer(8);
          const view1=new Int32Array(buffer); 
          const view2=new Int32Array(buffer, 4); 
          console.log(view1, view2);
          

          輸出結果如下:

          ② BYTES_PER_ELEMENT

          每種視圖的構造函數都有一個 BYTES_PER_ELEMENT 屬性,表示這種數據類型占據的字節數:

          Int8Array.BYTES_PER_ELEMENT // 1
          Uint8Array.BYTES_PER_ELEMENT // 1
          Int16Array.BYTES_PER_ELEMENT // 2
          Uint16Array.BYTES_PER_ELEMENT // 2
          Int32Array.BYTES_PER_ELEMENT // 4
          Uint32Array.BYTES_PER_ELEMENT // 4
          Float32Array.BYTES_PER_ELEMENT // 4
          Float64Array.BYTES_PER_ELEMENT // 8
          

          BYTES_PER_ELEMENT 屬性也可以在類型化數組的實例上獲取:

          const buffer=new ArrayBuffer(16); 
          const view=new Uint32Array(buffer); 
          console.log(Uint32Array.BYTES_PER_ELEMENT); // 4
          

          ③TypedArray.prototype.buffer

          TypedArray 實例的 buffer 屬性會返回內存中對應的 ArrayBuffer對象,只讀屬性。

          const a=new Uint32Array(8);
          const b=new Int32Array(a.buffer); 
          console.log(a, b);
          

          輸出結果如下:

          ④ TypedArray.prototype.slice()

          TypeArray 實例的 slice方法可以返回一個指定位置的新的 TypedArray實例。

          const view=new Int16Array(8);
          console.log(view.slice(0 ,5));
          

          輸出結果如下:

          ⑤ byteLength 和 length

          • byteLength:返回 TypedArray 占據的內存長度,單位為字節;
          • length:返回 TypedArray 元素個數;
          const view=new Int16Array(8);
          view.length;      // 8
          view.byteLength;  // 16
          

          (3)DataView

          說完 ArrayBuffer,下面來看看另一種操作 ArrayBuffer 的方式:DataView。DataView 視圖是一個可以從 二進制 ArrayBuffer 對象中讀寫多種數值類型的底層接口,使用它時,不用考慮不同平臺的字節序問題。

          DataView視圖提供更多操作選項,而且支持設定字節序。本來,在設計目的上,ArrayBuffer對象的各種TypedArray視圖,是用來向網卡、聲卡之類的本機設備傳送數據,所以使用本機的字節序就可以了;而DataView視圖的設計目的,是用來處理網絡設備傳來的數據,所以大端字節序或小端字節序是可以自行設定的。

          ① new DataView()

          DataView視圖可以通過構造函數來創建,它的參數是一個ArrayBuffer對象,生成視圖。其語法如下:

          new DataView(buffer [, byteOffset [, byteLength]])
          

          其有三個參數:

          • buffer:一個已經存在的 ArrayBuffer 對象,DataView 對象的數據源。
          • byteOffset:可選,此 DataView 對象的第一個字節在 buffer 中的字節偏移。如果未指定,則默認從第一個字節開始。
          • byteLength:可選,此 DataView 對象的字節長度。如果未指定,這個視圖的長度將匹配 buffer 的長度。

          來看一個例子:

          const buffer=new ArrayBuffer(16);
          const view=new DataView(buffer);
          console.log(view);
          

          打印結果如下:

          ② buffer、byteLength、byteOffset

          DataView 實例有以下常用屬性:

          • buffer:返回對應的ArrayBuffer對象;
          • byteLength:返回占據的內存字節長度;
          • byteOffset:返回當前視圖從對應的ArrayBuffer對象的哪個字節開始。
          const buffer=new ArrayBuffer(16);
          const view=new DataView(buffer);
          view.buffer;
          view.byteLength;
          view.byteOffset;
          

          打印結果如下:

          ③ 讀取內存

          DataView 實例提供了以下方法來讀取內存,它們的參數都是一個字節序號,表示開始讀取的字節位置:

          • getInt8:讀取1個字節,返回一個8位整數。
          • getUint8:讀取1個字節,返回一個無符號的8位整數。
          • getInt16:讀取2個字節,返回一個16位整數。
          • getUint16:讀取2個字節,返回一個無符號的16位整數。
          • getInt32:讀取4個字節,返回一個32位整數。
          • getUint32:讀取4個字節,返回一個無符號的32位整數。
          • getFloat32:讀取4個字節,返回一個32位浮點數。
          • getFloat64:讀取8個字節,返回一個64位浮點數。

          下面來看一個例子:

          const buffer=new ArrayBuffer(24);
          const view=new DataView(buffer);
          
          // 從第1個字節讀取一個8位無符號整數
          const view1=view.getUint8(0);
          
          // 從第2個字節讀取一個16位無符號整數
          const view2=view.getUint16(1);
          
          // 從第4個字節讀取一個16位無符號整數
          const view3=view.getUint16(3);
          

          ④ 寫入內存

          DataView 實例提供了以下方法來寫入內存,它們都接受兩個參數,第一個參數表示開始寫入數據的字節序號,第二個參數為寫入的數據:

          • setInt8:寫入1個字節的8位整數。
          • setUint8:寫入1個字節的8位無符號整數。
          • setInt16:寫入2個字節的16位整數。
          • setUint16:寫入2個字節的16位無符號整數。
          • setInt32:寫入4個字節的32位整數。
          • setUint32:寫入4個字節的32位無符號整數。
          • setFloat32:寫入4個字節的32位浮點數。
          • setFloat64:寫入8個字節的64位浮點數。

          5. Object URL

          Object URL(MDN定義名稱)又稱Blob URL(W3C定義名稱),是HTML5中的新標準。它是一個用來表示File Object 或Blob Object 的URL。在網頁中,我們可能會看到過這種形式的 Blob URL:

          其實 Blob URL/Object URL 是一種偽協議,允許將 Blob 和 File 對象用作圖像、二進制數據下載鏈接等的 URL 源。

          對于 Blob/File 對象,可以使用 URL構造函數的 createObjectURL() 方法創建將給出的對象的 URL。這個 URL 對象表示指定的 File 對象或 Blob 對象。我們可以在<img><script> 標簽中或者 <a><link> 標簽的 href 屬性中使用這個 URL。

          來看一個簡單的例子,首先定義一個文件上傳的 input 和一個 圖片預覽的 img:

          <input type="file" id="fileInput" />
          
          <img id="preview" />
          

          再來使用 URL.createObjectURL() 將File 對象轉化為一個 URL:

          const fileInput=document.getElementById("fileInput");
          const preview=document.getElementById("preview");
          
          fileInput.onchange=(e)=> {
            preview.src=URL.createObjectURL(e.target.files[0]);
            console.log(preview.src);
          };
          

          可以看到,上傳的圖片轉化成了一個 URL,并顯示在了屏幕上:

          那這個 API 有什么意義呢?可以將Blob/File對象轉化為URL,通過這個URL 就可以實現文件下載或者圖片顯示等。

          當我們使用createObjectURL()方法創建一個data URL 時,就需要使用revokeObjectURL()方法從內存中清除它來釋放內存。雖然瀏覽器會在文檔卸載時自動釋放 Data URL,但為了提高性能,我們應該使用revokeObjectURL()來手動釋放它。revokeObjectURL()方法接受一個Data URL 作為其參數,返回undefined。下面來看一個例子:

          const objUrl=URL.createObjectURL(new File([""], "filename"));
          console.log(objUrl);
          URL.revokeObjectURL(objUrl);
          

          6. Base64

          Base64 是一種基于64個可打印字符來表示二進制數據的表示方法。Base64 編碼普遍應用于需要通過被設計為處理文本數據的媒介上儲存和傳輸二進制數據而需要編碼該二進制數據的場景。這樣是為了保證數據的完整并且不用在傳輸過程中修改這些數據。

          在 JavaScript 中,有兩個函數被分別用來處理解碼和編碼 base64 字符串:

          • atob():解碼,解碼一個 Base64 字符串;
          • btoa():編碼,從一個字符串或者二進制數據編碼一個 Base64 字符串。
          btoa("JavaScript")       // 'SmF2YVNjcmlwdA=='
          atob('SmF2YVNjcmlwdA==') // 'JavaScript'
          

          那 base64 的實際應用場景有哪些呢?其實多數場景就是基于Data URL的。比如,使用toDataURL()方法把 canvas 畫布內容生成 base64 編碼格式的圖片:

          const canvas=document.getElementById('canvas'); 
          const ctx=canvas.getContext("2d");
          const dataUrl=canvas.toDataURL();
          

          除此之外,還可以使用readAsDataURL()方法把上傳的文件轉為base64格式的data URI,比如上傳頭像展示或者編輯:

          <input type="file" id="fileInput" />
          
          <img id="preview" />
          
          const fileInput=document.getElementById("fileInput");
          const preview=document.getElementById("preview");
          const reader=new FileReader();
          
          fileInput.onchange=(e)=> {
            reader.readAsDataURL(e.target.files[0]);
          };
          
          reader.onload=(e)=> {
            preview.src=e.target.result;
            console.log(e.target.result);
          };
          

          效果如下,將圖片(二進制數據)轉化為可打印的字符,也便于數據的傳輸:

          另外,一些小的圖片都可以使用 base64 格式進行展示,img標簽和backgroundurl 屬性都支持使用base64 格式的圖片,這樣做也可以減少 HTTP 請求。

          7. 格式轉化

          看完這些基本的概念,下面就來看看常用格式之間是如何轉換的。

          (1)ArrayBuffer → blob

          const blob=new Blob([new Uint8Array(buffer, byteOffset, length)]);
          

          (2)ArrayBuffer → base64

          const base64=btoa(String.fromCharCode.apply(null, new Uint8Array(arrayBuffer)));
          

          (3)base64 → blob

          const base64toBlob=(base64Data, contentType, sliceSize)=> {
            const byteCharacters=atob(base64Data);
            const byteArrays=[];
          
            for (let offset=0; offset < byteCharacters.length; offset +=sliceSize) {
              const slice=byteCharacters.slice(offset, offset + sliceSize);
          
              const byteNumbers=new Array(slice.length);
              for (let i=0; i < slice.length; i++) {
                byteNumbers[i]=slice.charCodeAt(i);
              }
          
              const byteArray=new Uint8Array(byteNumbers);
              byteArrays.push(byteArray);
            }
          
            const blob=new Blob(byteArrays, {type: contentType});
            return blob;
          }
          

          (4)blob → ArrayBuffer

          function blobToArrayBuffer(blob) { 
            return new Promise((resolve, reject)=> {
                const reader=new FileReader();
                reader.onload=()=> resolve(reader.result);
                reader.onerror=()=> reject;
                reader.readAsArrayBuffer(blob);
            });
          }
          

          (5)blob → base64

          function blobToBase64(blob) {
            return new Promise((resolve)=> {
              const reader=new FileReader();
              reader.onloadend=()=> resolve(reader.result);
              reader.readAsDataURL(blob);
            });
          }
          

          (6)blob → Object URL

          const objectUrl=URL.createObjectURL(blob);



          事背景

          昨天下午被問到一個問題:oss 對象存儲里邊由于有些圖片被共享,導致上傳了很多的重復的圖片或者文件,有沒有辦法在上傳之前判斷一下這個文件是否被上傳過,如果上傳過直接去后端拿存儲的地址行不行。

          當時被問到的時候,第一反應是根據file的文件類型名稱和大小生成一個MD5,后來被否決了,假如文件改了名字的話,這個文件還是會被上傳上去

          然后通過一天的調研,學習了這個之前沒有用過的FileReader對象,順便被他的其他方法給吸引住了,今天這里分享一下



          轉載鏈接:https://segmentfault.com/a/1190000022113605

          是什么FileReader

          FileReader 對象允許Web應用程序異步讀取存儲在用戶計算機上的文件(或原始數據緩沖區)的內容,使用File或Blob

          Blob 對象表示一個不可變、原始數據的類文件對象。Blob 表示的不一定是JavaScript原生格式的數據。

          File 接口基于Blob,繼承了 blob 的功能并將其擴展使其支持用戶系統上的文件。") 對象指定要讀取的文件或數據。

          其中File對象可以是來自用戶在一個 input 元素用于為基于Web的表單創建交互式控件,以便接受來自用戶的數據; 可以使用各種類型的輸入數據和控件小部件,具體取決于設備和user agent元素上選擇文件后返回的FileList對象,也可以來自拖放操作生成的DataTransfer對象,還可以是來自在一個HTMLCanvasElement上執行mozGetAsFile()方法后返回結果。(MDN)

          說白了就是FileReader對象可以對內存中的數據進行操作

          然后需要知道一個重點就是

          FileReader僅用于以安全的方式從用戶(遠程)系統讀取文件內容 它不能用于從文件系統中按路徑名簡單地讀取文件。

          也就是說他是不可以直接用本地的路徑去讀取文件的,可以請求后端的資源,來讀取對應的文件,或者前端以一個比較安全的方式讀取文件,常見的比如說input的文件上傳

          FileReader 的屬性

          打印一下 如圖

          
          EMPTY: 0
          LOADING: 1
          DONE: 2
          

          這三個是對象實例的狀態,分別是未讀取文件,正在讀取和讀取完畢

              readyState: [Exception: TypeError: Illegal invocation at FileReader.invokeGetter (<anonymous>:1:142)]
              result: [Exception: TypeError: Illegal invocation at FileReader.invokeGetter (<anonymous>:1:142)]
              error: [Exception: TypeError: Illegal invocation at FileReader.invokeGetter (<anonymous>:1:142)]

          然后這三個屬性

          • readyState,這個是獲取當前對象實例的狀態
          • result,這個是讀取文件成功之后的返回值,具體的返回值是根據,你調用的對象實例的方法而返回的,下邊我會講一下FileReader的方法的具體使用
          • error,顯而易見,報錯的時候的信息
          
              onloadstart: [Exception: TypeError: Illegal invocation at FileReader.invokeGetter (<anonymous>:1:142)]
              onprogress: [Exception: TypeError: Illegal invocation at FileReader.invokeGetter (<anonymous>:1:142)]
              onload: [Exception: TypeError: Illegal invocation at FileReader.invokeGetter (<anonymous>:1:142)]
              onabort: [Exception: TypeError: Illegal invocation at FileReader.invokeGetter (<anonymous>:1:142)]
              onerror: [Exception: TypeError: Illegal invocation at FileReader.invokeGetter (<anonymous>:1:142)]
              onloadend: [Exception: TypeError: Illegal invocation at FileReader.invokeGetter (<anonymous>:1:142)]
              
          • onloadstart 讀取開始時觸發,這里可以做一些常用的處理 比如加載loading什么的
          • onprogress 這個事件就比較讓人喜歡了,這個方法會在文件被讀取的過程中被觸發,每大概11927552字節左右會被觸發一次,這個里邊會反給一個ProgressEvent 對象,這個對象里邊有本次讀取文件的最大字節數和已經讀取完畢的字節數,可以用來做進度條什么的
          • onload 這個事件是文件讀取成功的時候觸發 ,在這里里邊可以使用上邊說道的實例上邊的result屬性,查看你操作的函數的對應的內容
          • onabort 讀取文件被終端的時候觸發,與之對應的有一個中斷讀取的方法
          • onerror 讀取文件失敗的時候觸發
          • onloadend 讀取文件 不管失敗還是成功都會觸發這個方法,這個方法的執行時機在onload方法之后

          FileReader 的方法

          • readAsDataURL , 這個方法會返回一個你得到的這個對象的一個base64的地址,但是這個地址,你會發現你的文件越大,這個地址就越長,其實這個地址是一個Base64編碼的文件數據字符串

          然后之前說了FileReader所有的操作都是異步的,所以你并不能像下邊這樣獲取返回值

                  let fileReader=new FileReader()
                  let url=fileReader.readAsDataURL(file.file)
                  console.log(url)

          這樣是打印不出來的,你需要在他自身的處理事件上邊回調獲取

                  let fileReader=new FileReader()
                  fileReader.readAsDataURL(file.file)
                  fileReader.onload=()=>{
                      console.log(fileReader.result)
                  }

          回調結果在對象實例的result屬性上邊,上邊有說過

          • readAsBinaryString 開始讀取指定的Blob中的內容。一旦完成,result屬性中將包含所讀取文件的原始二進制數據。

          這個方法獲取的結果是原始二進制數據,不能直接使用,還需要做一些轉換或者使用標簽什么得才能用,打印出來大概是這樣的

          • abort 這個是中斷文件的讀取,比如你覺得這個讀取的事件有點長,再或者在某個特定情況下你希望他停下來,那么這個時候可以使用這個方法中斷他,并且使用這個方法之后fileReader對象的狀態是DONE 也就是說可以在onload里邊去獲取已經讀取的數據

          readAsArrayBuffer

          • 最后這兩個是我今天用到的方法readAsArrayBuffer 開始讀取指定的Blob中的內容, 一旦完成, result 屬性中保存的將是被讀取文件的 ArrayBuffer數據對象.這個ArrayBuffer上邊有mdn的傳送門可以看下,或者看一下阮一峰老師的es6最下邊的講解,他是一個字節數組,用來表示通用的、固定長度的原始二進制數據緩沖區他不能直接被操作,你可以用對應的TypedArray接口或者DataView的接口來操作他,這是一個對二進制字節數據操作的底層接口,我這里使用了TypeArray
          • TypeArray的常用構造函數Int8Array:8 位有符號整數,長度 1 個字節。Uint8Array:8 位無符號整數,長度 1 個字節。Uint8ClampedArray:8 位無符號整數,長度 1 個字節,溢出處理不同。Int16Array:16 位有符號整數,長度 2 個字節。Uint16Array:16 位無符號整數,長度 2 個字節。Int32Array:32 位有符號整數,長度 4 個字節。Uint32Array:32 位無符號整數,長度 4 個字節。Float32Array:32 位浮點數,長度 4 個字節。Float64Array:64 位浮點數,長度 8 個字節。

          好,看到這里,之前沒有接觸過的同學是不是腦瓜子嗡嗡的。。沒關系 我昨天我也嗡嗡的。。。

          簡單扼要的說一下就是說,上邊列出來的這九個構造函數,都會根據你傳進去的參數,生成一個對應的數組,然后這些數組統稱為TypeArray視圖,這個數組包含了所有的數組的方法和屬性,你可以像數組一樣去操作他們,一會我會在下邊打印一下他們的結果,看一下就知道了

          • 然后上邊說的DataView,簡單說一下這個DataView是和TypedArray 配套使用的,因為DataView的參數是接受一個TypedArray對象,具體方法如下
          • 讀取getInt8:讀取 1 個字節,返回一個 8 位整數。getUint8:讀取 1 個字節,返回一個無符號的 8 位整數。getInt16:讀取 2 個字節,返回一個 16 位整數。getUint16:讀取 2 個字節,返回一個無符號的 16 位整數。getInt32:讀取 4 個字節,返回一個 32 位整數。getUint32:讀取 4 個字節,返回一個無符號的 32 位整數。getFloat32:讀取 4 個字節,返回一個 32 位浮點數。getFloat64:讀取 8 個字節,返回一個 64 位浮點數。
          • 寫入setInt8:寫入 1 個字節的 8 位整數。setUint8:寫入 1 個字節的 8 位無符號整數。setInt16:寫入 2 個字節的 16 位整數。setUint16:寫入 2 個字節的 16 位無符號整數。setInt32:寫入 4 個字節的 32 位整數。setUint32:寫入 4 個字節的 32 位無符號整數。setFloat32:寫入 4 個字節的 32 位浮點數。setFloat64:寫入 8 個字節的 64 位浮點數。

          readAsText

          這個是之前的時候搞得一個讀取文件的方法,里邊用到了FileReader的readAsText方法,不多說廢話了,直接附上代碼和效果圖

          
          
          export default function readFile(model) {
              return new Promise((resolve)=> {
                  // 谷歌
                  if (window.FileReader) {
                      // 獲取文件流
                      let file=model.currentTarget ? model.currentTarget.files[0] : model;
                      // 創建FileReader實例
                      let reader=new FileReader();
                      // 讀文件
                      reader.readAsText(file);
                      reader.onload=()=> {
                          resolve(reader.result)
                      }
                  }
                  //支持IE 7 8 9 10
                  else if (typeof window.ActiveXObject !='undefined') {
                      let xmlDoc;
                      xmlDoc=new ActiveXObject("Microsoft.XMLDOM");
                      xmlDoc.async=false;
                      resolve(xmlDoc.load(model))
                  }
                  //支持FF
                  else if (document.implementation && document.implementation.createDocument) {
                      let xmlDoc;
                      xmlDoc=document.implementation.createDocument("", "", null);
                      xmlDoc.async=false;
                      resolve(xmlDoc.load(model))
                  }
              })
          }
             
          //安裝依賴
          npm i zjsmethods -S
          ~~~~
          // 頁面引入并使用
           import { _readFile } from "zjsmethods"
          _readFile(file).then(res=>{
              console.log(res)
          })
          

          readAsArrayBuffer校驗文件唯一

          上邊說了那么一大堆,終于要進入正題了哈,直接看第一版代碼

              const reader=new FileReader();
              reader.readAsArrayBuffer(file.file);
              reader.onload=()=> {
                     let u8Arr=new Uint8Array(reader.result)
                     console.log(u8Arr)
                     console.log(md5(u8Arr))
              }
              

          ok 沒得問題,結果如下

          正在我覺得如此簡單的時候,意外發生了,在我用比較小的文件的時候,只有1M 左右,但是當我上傳了一個視頻做測試的時候大概有兩個G,瀏覽器崩潰了。。崩潰了。。了

          然后我展開了 之前比較小文件的字節數組,大概有這么大

          原因是readAsArrayBuffer在讀取文件的時候會先把整個文件加載到內存中,那么如果文件太大,內存就不夠用了,瀏覽器進程就會崩潰。

          既然整個加載不行,那么我們選擇把一個文件分段加載,后來我覺得10M一段比較穩妥,于是改成了當文件小于10M的時候平均分成10段,如果大于10M ,那么每10M 分成一段 直到分完為止,同樣為了避免加密的時候數據太多造成卡頓,在生成標識的時候放棄用整個數組生成標識,采取固定規則的最大10M 數據生成標識

              async vaildArrayBuffer(){
                  const reader=new FileReader();
                  while(this.whileNumber--){
                      this.start=this.end
                      this.end=this.end+this.whileMax
                      let { start,end,sliceEnd,file}=this
                          reader.readAsArrayBuffer(file.slice(start,end));
                          reader.onload=()=> {
                                  new Uint8Array(reader.result)
                                      .slice(0, sliceEnd)
                                      .join('')
                          }
                  }
              }

          這個時候又出了一個小插曲,在調用的時候reader被提示,正在進行文件讀取,也是就一個reader在做讀取文件操作的時候不能同事讀取兩個,于是乎剛開始的時候我高估了讀取的速度放在了回調里邊讀取文件,代價就是我在電腦前面眼巴巴的看了控制臺大概5分鐘,后來改成了promise包裹,最后整理出的代碼如下

          /*
           * @Date: 2020-03-22 16:36:37
           * @information: 最后更新時間
           */
          import md5 from 'md5'
          export default class vaileFile{
              constructor(file){
                  this.file=file
                  // 每次截取多少二進制
                  this.whileMax=Math.floor(file.size / 10 > 10240 * 1024 ?  10240 * 1024 : file.size / 10);
                  // 循環截取多少次
                  this.whileNumber=file.size <=10240 * 1024 ? 10 : Math.ceil(file.size/this.whileMax)
                  // 二進制的截取長度,超出10M后 每10M 截取一部分,最多10M
                  this.sliceEnd=Math.floor(1024 * 10240 / file.size * 100 / this.whileNumber * this.whileMax)
                  this.sliceEnd=this.whileNumber>10?this.sliceEnd:10240 * 1024 
                  // 轉換二進制的長度
                  this.start=0 
                  this.end=0;
              }
              /**
               * @Author: 周靖松
               * @Date: 2020-03-22 15:53:07
               * @information: 校驗文件唯一
               */
              async vaildArrayBuffer(){
                      let promiseArr=[]
                      while(this.whileNumber--){
                          this.start=this.end
                          this.end=this.end+this.whileMax
                          let { start,end,sliceEnd,file}=this
                          let promiseArrayBuffer=new Promise((resolve,reject)=>{
                              const reader=new FileReader();
                              reader.readAsArrayBuffer(file.slice(start,end));
                              reader.onload=()=> {
                                  resolve(
                                      new Uint8Array(reader.result)
                                          .slice(0, sliceEnd)
                                          .join('')
                                  )
                              }
                          })
                          promiseArr.push(promiseArrayBuffer)
                      }
                      return md5((await Promise.all(promiseArr)).join(''))
                  }
              }

          大功告成,上傳的文件后會生成一個md5 ,復制文件,文件改名字,都可以識別是之前的文件

          然后寫一個README.md 說明一下使用方法

          ### _vaileFile ,//使用文件二進制校驗文件唯一性
          
              當有業務需要上傳oss 對象存儲的時候,為了避免同一個文件(視頻,音頻,圖片,壓縮包等),有可能其他人復制或者改名字等等,造成文件重復上傳,大量占用空間,寫了一個校驗文件的方法
          
              //安裝依賴
              npm i zjsmethods -S
              
              //引入這個類
          
              import { _vaileFile } from 'zjsmethods'
          
              //  然后在你需要判斷oss 是否有該文件的時候
          
              new _vaileFile('file對象').vaildArrayBuffer().then(res=>{
                  console.log(res)
                  // 繼續上傳 或者 向后端請求已經存在的文件url
              })
          
              // new 這個類之后 有一個vaildArrayBuffer 方法 他返回一個promise ,里邊返回值是一個md5的字符串,這個是這個文件的唯一標識

          喜歡的點個贊吧,有不足之處歡迎斧正


          主站蜘蛛池模板: 日本不卡在线一区二区三区视频| 亚洲一区无码中文字幕乱码| 亚洲一区二区三区成人网站| 精品一区二区久久| 中文字幕日韩一区二区三区不| 日本精品一区二区在线播放| 午夜影院一区二区| 秋霞日韩一区二区三区在线观看| 精品一区二区三区在线播放 | 3D动漫精品啪啪一区二区下载| 亚洲AV成人精品日韩一区| 中文字幕一区二区三区5566| 国产一区二区三区免费观在线| 中文字幕一区二区三区日韩精品| 午夜视频久久久久一区| 国产精品一区在线观看你懂的| 亚洲AV无码一区东京热久久| 亚洲综合一区国产精品| 亚洲国模精品一区| 久久无码精品一区二区三区| 国产亚洲3p无码一区二区| 国产成人亚洲综合一区| 激情爆乳一区二区三区| 99精品一区二区免费视频| 波多野结衣在线观看一区| 精品无码一区二区三区亚洲桃色| 精品无码国产AV一区二区三区| 视频一区在线免费观看| 一区二区三区在线播放视频| 国产日韩精品一区二区在线观看| 加勒比无码一区二区三区| 国产aⅴ精品一区二区三区久久| 日本一区免费电影| 国产成人无码一区二区三区| 国产成人精品一区二区三区免费| 高清一区二区在线观看| 日韩美一区二区三区| 精品亚洲av无码一区二区柚蜜| 一区二区在线免费观看| 免费一区二区三区四区五区| 一区二区三区四区在线观看视频 |