學習時間:2020.06.06
學習章節:《你不知道的 Blob》
原文對 Blob 的知識點介紹得很是完整清晰,本文經過四個問題來總結本文核心知識:javascript
Blob(Binary Large Object)表示二進制類型的大對象,一般是影像、聲音或多媒體文件。MySql/Oracle數據庫中,就有一種Blob類型,專門存放二進制數據。在 JavaScript 中 Blob 對象表示一個不可變、原始數據的類文件對象,它不必定非得是大量數據,也能夠表示一個小型文件的內容。
另外,JavaScript 中的 File 接口是基於 Blob,繼承 Blob 的功能並將其擴展使其支持用戶系統上的文件。
html
Blob
由一個可選字符串 type
和 blobParts
組成,其中, type
一般爲 MIME 類型。前端
MIME(Multipurpose Internet Mail Extensions)多用途互聯網郵件擴展類型,常見有:超文本標記語言文本 .htmltext/html
、PNG圖像 .pngimage/png
、普通文本 .txttext/plain
等。
Blob 構造函數語法爲:java
const myBlob = new Blob(blobParts[, options])
入參:git
blobParts
:它是一個由 ArrayBuffer,ArrayBufferView,Blob,DOMString 等對象構成的數組。DOMStrings 會被編碼爲 UTF-8。options
:一個可選的對象,包含如下兩個屬性:github
type
:默認值爲 ""
,表示將會被放入到 blob 中的數組內容的 MIME 類型。endings
:默認值爲 "transparent"
,用於指定包含行結束符 \n
的字符串如何被寫入。它是如下兩個值中的一個:"native"
,表明行結束符會被更改成適合宿主操做系統文件系統的換行符,或者 "transparent"
,表明會保持 blob 中保存的結束符不變。出參:
返回一個新建立的 Blob 對象,其內容由參數中給定的數組串聯組成。
web
Blob
對象擁有 2 個屬性:數據庫
size
:只讀,表示 Blob
對象中所包含的數據大小(以字節爲單位);type
:只讀,值爲字符串,表示該 Blob
對象所包含數據的 MIME 類型。若類型未知,則該屬性值爲空字符串。slice([start[, end[, contentType]]])
:返回一個新的 Blob 對象,包含了源 Blob 對象中指定範圍內的數據。stream()
:返回一個能讀取 Blob 內容的 ReadableStream
。text()
:返回一個 Promise 對象且包含 Blob 全部內容的 UTF-8 格式的 USVString
。arrayBuffer()
:返回一個 Promise 對象且包含 Blob 全部內容的二進制格式的 ArrayBuffer
。
注意: Blob
對象是不可改變的,可是能夠進行分割,並建立出新的 Blob
對象,將它們混合到一個新的 Blob
中。相似於 JavaScript 字符串:咱們沒法更改字符串中的字符,但能夠建立新的更正後的字符串。
canvas
let myBlobParts = ['<html><h2>Hello Leo</h2></html>']; // 一個包含DOMString的數組 let myBlob = new Blob(myBlobParts, {type : 'text/html', endings: "transparent"}); // 獲得 blob console.log(myBlob.size + " bytes size"); // Output: 31 bytes size console.log(myBlob.type + " is the type"); // Output: text/html is the type
JavaScript類型化數組是一種相似數組的對象,並提供了一種用於 訪問原始二進制數據的機制 。而且在類型數組上調用 Array.isArray()
會返回 false
。
詳細可參考MDN《JavaScript 類型化數組》章節。數組
let hello = new Uint8Array([72, 101, 108, 108, 111]); // 二進制格式的 "hello" let blob = new Blob([hello, ' ', 'leo'], {type: 'text/plain'}); // Output: "Hello leo"
因爲 Blob
對象是不可改變的,但咱們能夠進行分割,並組裝成一個新的 Blob
對象:
let blob1 = new Blob(['<html><h2>Hello Leo</h2></html>'], {type : 'text/html', endings: "transparent"}); let blob2 = new Blob(['<html><h2>Happy Boy!</h2></html>'], {type : 'text/html', endings: "transparent"}); let slice1 = blob1.slice(16); let slice2 = blob2.slice(0, 16); await slice1.text(); // currtent slice1 value: "Leo</h2></html>" await slice2.text(); // currtent slice2 value: "<html><h2>Happy " let newBlob = new Blob([slice2, slice1], {type : 'text/html', endings: "transparent"}); await newBlob.text(); // currtent newBlob value: "<html><h2>Happy Leo</h2></html>"
這裏整理 2 種圖片本地預覽的方式:
<body> <h1>1.DataURL方式:</h1> <input type="file" accept="image/*" onchange="selectFileForDataURL(event)"> <img id="output1"> <h1>2.Blob方式:</h1> <input type="file" accept="image/*" onchange="selectFileForBlob(event)"> <img id="output2"> <script> // 1.DataURL方式: async function selectFileForDataURL() { const reader = new FileReader(); reader.onload = function () { const output = document.querySelector("#output1") output.src = reader.result; } reader.readAsDataURL(event.target.files[0]); } //2.Blob方式: async function selectFileForBlob(){ const reader = new FileReader(); const output = document.querySelector("#output2"); const imgUrl = window.URL.createObjectURL(event.target.files[0]); output.src = imgUrl; reader.onload = function(event){ window.URL.revokeObjectURL(imgUrl); } } </script> </body>
上面主要介紹 Blob URL
和 Data URL
兩種方式實現圖片本地預覽,這兩個類型的區別在《5、拓展》中介紹。
實現本地預覽:
將 input
獲取到的 file
對象,經過實例化 FileReader
,賦值給變量 reader
,調用reader
的 readAsDataURL
方法,將 file
對象轉換爲 dataURL
,而後監聽 reader
的 onload
屬性,獲取到讀取結果 result
,而後設置爲圖片的 src
值。
實現分片上傳:
因爲 File 是特殊類型的 Blob,可用於任意 Blob 類型的上下文,因此針對大文件傳輸,咱們可使用 slice
方法進行文件切割,分片上傳。
<body> <input type="file" accept="image/*" onchange="selectFile(event)"> <button onclick="upload()">上傳</button> <img id="output"> <script> const chunkSize = 10000; const url = "https://httpbin.org/post"; async function selectFile(){ // 本地預覽 const reader = new FileReader(); reader.onload = function(){ const output = document.querySelector("#output") output.src = reader.result; } reader.readAsDataURL(event.target.files[0]); // 分片上傳 await upload(event.target.files[0]); } async function upload(files){ const file = files; for(let start = 0; start < file.size; start += chunkSize){ const chunk = file.slice(start, start + chunkSize + 1); const fd = new FormData(); fd.append("data", chunk); await fetch(url, { method: "post", body: fd }).then((res) =>{ console.log(res) res.text(); }); } } </script> </body>
<body> <input type="file" accept="image/*" onchange="selectFile(event)"> <button onclick="upload()">上傳</button> <button onclick="pause()">暫停</button> <button onclick="continues()">繼續</button> <img id="output" src="" alt=""> <script> const chunkSize = 30000; let start = 0, curFile, isPause = false; const url = "https://httpbin.org/post"; async function selectFile(){ const reader = new FileReader(); reader.onload = function(){ const output = document.querySelector("#output") output.src = reader.result; } reader.readAsDataURL(event.target.files[0]); curFile = event.target.files[0]; } async function upload(){ const file = curFile; for(start; start < file.size; start += chunkSize){ if(isPause) return; const chunk = file.slice(start, start + chunkSize + 1); const fd = new FormData(); fd.append("data", chunk); await fetch(url, { method: "post", body: fd }).then((res) =>{ res.text() } ); if(chunk.size < chunkSize){ uploadSuccess(); return; } } } function pause(){ isPause = true; } function continues(){ isPause = false; upload(); } function uploadSuccess(){ isPause = false; start = 0; } </script> </body>
在實現「從互聯網下載數據」方法時,咱們使用 createObjectURL
顯示圖片,在請求互聯網圖片時,咱們有兩種方式:
XMLHttpRequest
;fetch
;<body> <button onclick="download1()">使用 XMLHttpRequest 下載</button> <button onclick="download2()">使用 fetch 下載</button> <img id="pingan"> <script> const url = "http://images.pingan8787.com/TinyCompiler/111.png"; const pingan = document.querySelector('#pingan'); function download1 (){ const xhr = new XMLHttpRequest(); xhr.open('GET', url); xhr.responseType = 'blob'; xhr.onload = () => { renderImage(xhr.response); } xhr.send(null); } function download2 (){ fetch(url).then(res => { return res.blob(); }).then(myBlob => { renderImage(myBlob); }) } function renderImage (data){ let objectURL = URL.createObjectURL(data); pingan.src = objectURL; // 根據業務須要手動調用 URL.revokeObjectURL(imgUrl) } </script> </body>
經過調用 Blob 的構造函數來建立類型爲 "text/plain"
的 Blob 對象,而後經過動態建立 a
標籤來實現文件的下載。
<body> <button onclick="download()">Blob 文件下載</button> <script> function download(){ const fileName= "Blob文件.txt"; const myBlob = new Blob(["一文完全掌握 Blob Web API"], { type: "text/plain" }); downloadFun(fileName, myBlob); } function downloadFun(fileName, blob){ const link = document.createElement("a"); link.href = URL.createObjectURL(blob); link.download = fileName; link.click(); link.remove(); URL.revokeObjectURL(link.href); } </script> </body>
當咱們但願本地圖片在上傳以前,先進行必定壓縮,再提交,從而減小傳輸的數據量。
在前端咱們可使用 Canvas 提供的 toDataURL()
方法來實現,該方法接收 type
和 encoderOptions
兩個可選參數:
type
表示圖片格式,默認爲 image/png
;encoderOptions
表示圖片質量,在指定圖片格式爲 image/jpeg
或 image/webp
的狀況下,能夠從 0 到 1 區間內選擇圖片質量。若是超出取值範圍,將會使用默認值 0.92
,其餘參數會被忽略。<body> <input type="file" accept="image/*" onchange="loadFile(event)" /> <script> // compress.js const MAX_WIDTH = 800; // 圖片最大寬度 // 圖片壓縮方法 function compress(base64, quality, mimeType) { let canvas = document.createElement("canvas"); let img = document.createElement("img"); img.crossOrigin = "anonymous"; return new Promise((resolve, reject) => { img.src = base64; img.onload = () => { let targetWidth, targetHeight; if (img.width > MAX_WIDTH) { targetWidth = MAX_WIDTH; targetHeight = (img.height * MAX_WIDTH) / img.width; } else { targetWidth = img.width; targetHeight = img.height; } canvas.width = targetWidth; canvas.height = targetHeight; let ctx = canvas.getContext("2d"); ctx.clearRect(0, 0, targetWidth, targetHeight); // 清除畫布 ctx.drawImage(img, 0, 0, canvas.width, canvas.height); let imageData = canvas.toDataURL(mimeType, quality / 100); // 設置圖片質量 resolve(imageData); }; }); } // 爲了進一步減小傳輸的數據量,咱們能夠把它轉換爲 Blob 對象 function dataUrlToBlob(base64, mimeType) { let bytes = window.atob(base64.split(",")[1]); let ab = new ArrayBuffer(bytes.length); let ia = new Uint8Array(ab); for (let i = 0; i < bytes.length; i++) { ia[i] = bytes.charCodeAt(i); } return new Blob([ab], { type: mimeType }); } // 經過 AJAX 提交到服務器 function uploadFile(url, blob) { let formData = new FormData(); let request = new XMLHttpRequest(); formData.append("image", blob); request.open("POST", url, true); request.send(formData); } function loadFile(event) { const reader = new FileReader(); reader.onload = async function () { let compressedDataURL = await compress( reader.result, 90, "image/jpeg" ); let compressedImageBlob = dataUrlToBlob(compressedDataURL); uploadFile("https://httpbin.org/post", compressedImageBlob); }; reader.readAsDataURL(event.target.files[0]); }; </script> </body>
其實 Canvas 對象除了提供 toDataURL()
方法以外,它還提供了一個 toBlob()
方法,該方法的語法以下:
canvas.toBlob(callback, mimeType, qualityArgument)
和 toDataURL()
方法相比,toBlob()
方法是異步的,所以多了個 callback
參數,這個 callback
回調方法默認的第一個參數就是轉換好的 blob
文件信息。
在瀏覽器端,利用一些現成的開源庫,好比 jsPDF,咱們也能夠方便地生成 PDF 文檔。
<body> <h3>客戶端生成 PDF 示例</h3> <script src="https://unpkg.com/jspdf@latest/dist/jspdf.min.js"></script> <script> (function generatePdf() { const doc = new jsPDF(); doc.text("Hello semlinker!", 66, 88); const blob = new Blob([doc.output()], { type: "application/pdf" }); blob.text().then((blobAsText) => { console.log(blobAsText); }); })(); </script> </body>
其實 jsPDF 除了支持純文本以外,它也能夠生成帶圖片的 PDF 文檔,好比:
let imgData = 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/...' let doc = new jsPDF(); doc.setFontSize(40); doc.text(35, 25, 'Paranyan loves jsPDF'); doc.addImage(imgData, 'JPEG', 15, 40, 180, 160);
ArrayBuffer
對象用於表示通用的,固定長度的原始二進制數據緩衝區。且不能直接操縱 ArrayBuffer 的內容,須要建立一個類型化數組對象或 DataView
對象,該對象以特定格式表示緩衝區,並使用該對象讀取和寫入緩衝區的內容。Blob
類型的對象表示不可變的相似文件對象的原始數據。Blob
表示的不必定是 JavaScript 原生格式的數據。File
接口基於 Blob
,繼承了Blob
功能並將其擴展爲支持用戶系統上的文件。Blob
類型只有 slice
方法,用於返回一個新的 Blob
對象,包含了源 Blob
對象中指定範圍內的數據。 對比發現,ArrayBuffer
的數據,是能夠按照字節去操做的,而 Blob
只能做爲一個完整對象去處理。因此說,ArrayBuffer
相比 Blob
更接近真實的二進制,更底層。
只需將 ArrayBuffer
做爲參數傳入便可:
const buffer = new ArrayBuffer(16); const blob = new Blob([buffer]);
須要藉助 FileReader
對象:
const blob = new Blob([1,2,3,4,5]); const reader = new FileReader(); reader.onload = function() { console.log(this.result); } reader.readAsArrayBuffer(blob);
function GET(url, callback) { let xhr = new XMLHttpRequest(); xhr.open('GET', url, true); xhr.responseType = 'arraybuffer'; // or xhr.responseType = "blob"; xhr.send(); xhr.onload = function(e) { if (xhr.status != 200) { alert("Unexpected status code " + xhr.status + " for " + url); return false; } callback(new Uint8Array(xhr.response)); // or new Blob([xhr.response]); }; }
Blob URL
格式如 blob:域名/uuid
, Data URL
格式如: data:[<mediatype>][;base64],<data>
。mediatype
是個 MIME 類型的字符串,例如 "image/jpeg
" 表示 JPEG 圖像文件。若是被省略,則默認值爲 text/plain;charset=US-ASCII
。
Blob URL
通常長度較短,而 Data URL
由於直接存儲圖片 base64 編碼後的數據,每每比較長。
Blob URL
能夠很方便使用 XMLHttpRequest 獲取源數據( xhr.responseType = 'blob'
),而 Data URL
並非全部瀏覽器都支持經過 XMLHttpRequest 獲取源數據的。
Blob URL
只能在當前應用內使用,把 Blob URL
複製到瀏覽器地址欄是沒法獲取數據,而 Data URL
則能夠在任意瀏覽器中使用。
本文中咱們主要經過 4 個問題來複習了 Blob 知識點:「Blob 是什麼」、「Blob 怎麼用」、「Blob 使用場景」和「Blob 與 ArrayBuffer 區別」,在「Blob 使用場景」部分中,也主要介紹了咱們實際開發中很是常見的「圖片預覽」、「圖片下載」和「生成文件」的場景。在文章最後,也經過和你們複習了「Blob URL 和 Data URL 區別」,讓咱們對 Blob 有更深的認識。
Author | 王平安 |
---|---|
pingan8787@qq.com | |
博 客 | www.pingan8787.com |
微 信 | pingan8787 |
每日文章推薦 | https://github.com/pingan8787... |
ES小冊 | js.pingan8787.com |
語雀知識庫 | Cute-FrontEnd |