《你不知道的 Blob》番外篇

學習時間:2020.06.06
學習章節:《你不知道的 Blob》


原文對 Blob 的知識點介紹得很是完整清晰,本文經過四個問題來總結本文核心知識:javascript

  1. Blob 是什麼?
  2. Blob 怎麼用?
  3. Blob 有哪些使用場景?
  4. Blob 與 ArrayBuffer 有何區別?

讀《你不知道的 Blob》總結.png

1、Blob 是什麼?

Blob(Binary Large Object)表示二進制類型的大對象,一般是影像、聲音或多媒體文件。MySql/Oracle數據庫中,就有一種Blob類型,專門存放二進制數據。在 JavaScript 中 Blob 對象表示一個不可變、原始數據的類文件對象,它不必定非得是大量數據,也能夠表示一個小型文件的內容。
另外,JavaScript 中的 File 接口是基於 Blob,繼承 Blob 的功能並將其擴展使其支持用戶系統上的文件。
html

2、Blob 怎麼用?

Blob 由一個可選字符串 typeblobParts 組成,其中, type 一般爲 MIME 類型。前端

MIME(Multipurpose Internet Mail Extensions)多用途互聯網郵件擴展類型,常見有:超文本標記語言文本 .html text/html 、PNG圖像 .png image/png 、普通文本 .txt text/plain  等。

1. 構造函數

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

2. 屬性和方法

2.1 屬性介紹

Blob 對象擁有 2 個屬性:數據庫

  • size :只讀,表示 Blob 對象中所包含的數據大小(以字節爲單位);
  • type :只讀,值爲字符串,表示該 Blob 對象所包含數據的 MIME 類型。若類型未知,則該屬性值爲空字符串。

2.2 方法介紹

  • slice([start[, end[, contentType]]]) :返回一個新的 Blob 對象,包含了源 Blob 對象中指定範圍內的數據。
  • stream():返回一個能讀取 Blob 內容的 ReadableStream
  • text():返回一個 Promise 對象且包含 Blob 全部內容的 UTF-8 格式的 USVString
  • arrayBuffer():返回一個 Promise 對象且包含 Blob 全部內容的二進制格式的 ArrayBuffer


注意: Blob 對象是不可改變的,可是能夠進行分割,並建立出新的 Blob 對象,將它們混合到一個新的 Blob  中。相似於 JavaScript 字符串:咱們沒法更改字符串中的字符,但能夠建立新的更正後的字符串。
canvas

3. 簡單上手

3.1 示例1:從字符串建立 Blob

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

3.2 示例2:從類型化數組和字符串建立 Blob

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"

3.3 示例3:組裝新的 Blob

因爲 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>"

3、Blob 有哪些使用場景?

1. 圖片本地預覽

這裏整理 2 種圖片本地預覽的方式:

  1. 使用 DataURL 方式;
  2. 使用 Blob URL/Object URL 方式;
<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 URLData URL 兩種方式實現圖片本地預覽,這兩個類型的區別在《5、拓展》中介紹。

2. 圖片本地預覽 + 分片上傳

實現本地預覽:
input 獲取到的 file 對象,經過實例化 FileReader ,賦值給變量 reader ,調用readerreadAsDataURL 方法,將 file 對象轉換爲  dataURL ,而後監聽 readeronload 屬性,獲取到讀取結果 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>

3. 圖片本地預覽 + 分片上傳 + 暫停 + 續傳

<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>

4. 從互聯網下載數據

在實現「從互聯網下載數據」方法時,咱們使用 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>

5. 下載文件

經過調用 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>

6. 圖片壓縮

當咱們但願本地圖片在上傳以前,先進行必定壓縮,再提交,從而減小傳輸的數據量。
在前端咱們可使用 Canvas 提供的 toDataURL() 方法來實現,該方法接收 typeencoderOptions 兩個可選參數:

  • type 表示圖片格式,默認爲 image/png
  • encoderOptions 表示圖片質量,在指定圖片格式爲 image/jpegimage/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文件信息。

7. 生成 PDF 文檔

在瀏覽器端,利用一些現成的開源庫,好比 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);

4、Blob 與 ArrayBuffer 有何區別?

1. 定義區別

ArrayBuffer 對象用於表示通用的,固定長度的原始二進制數據緩衝區。且不能直接操縱 ArrayBuffer 的內容,須要建立一個類型化數組對象或 DataView 對象,該對象以特定格式表示緩衝區,並使用該對象讀取和寫入緩衝區的內容。


Blob 類型的對象表示不可變的相似文件對象的原始數據Blob 表示的不必定是 JavaScript 原生格式的數據。File 接口基於 Blob,繼承了Blob 功能並將其擴展爲支持用戶系統上的文件。


Blob 類型只有 slice 方法,用於返回一個新的 Blob 對象,包含了源 Blob 對象中指定範圍內的數據。 對比發現,ArrayBuffer 的數據,是能夠按照字節去操做的,而 Blob 只能做爲一個完整對象去處理。因此說,ArrayBuffer 相比 Blob 更接近真實的二進制,更底層。

2. 二者互轉

2.1 ArrayBuffer 轉 Blob

只需將 ArrayBuffer 做爲參數傳入便可:

const buffer = new ArrayBuffer(16);
const blob = new Blob([buffer]);

2.2 Blob 轉 ArrayBuffer

須要藉助 FileReader 對象:

const blob = new Blob([1,2,3,4,5]);
const reader = new FileReader();

reader.onload = function() {
    console.log(this.result);
}
reader.readAsArrayBuffer(blob);

3. 其餘區別

  1. 須要使用寫入/編輯操做時使用 ArrayBuffer,不然使用 Blob 便可;
  2. Blob 對象不可變,而 ArrayBuffer 能夠經過 TypedArrays 或 DataView 操做;
  3. Blob 能夠位於磁盤、高速緩存內存和其餘不一樣用位置,而 ArrayBuffer 存在內存中,能夠直接操做;

4. Ajax 中使用 Blob 和 ArrayBuffer

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]);
  };
}

5、拓展

1. Blob URL 和 Data URL 區別

1.1 格式不一樣

Blob URL 格式如 blob:域名/uuidData URL 格式如: data:[<mediatype>][;base64],<data>  。
mediatype 是個 MIME 類型的字符串,例如 "image/jpeg" 表示 JPEG 圖像文件。若是被省略,則默認值爲 text/plain;charset=US-ASCII
image.png

1.2 長度不一樣

Blob URL 通常長度較短,而 Data URL 由於直接存儲圖片 base64 編碼後的數據,每每比較長。

1.3 XMLHttpRequest 支持狀況不一樣

Blob URL  能夠很方便使用 XMLHttpRequest 獲取源數據( xhr.responseType = 'blob' ),而 Data URL 並非全部瀏覽器都支持經過 XMLHttpRequest 獲取源數據的。

1.4 使用場景不一樣

Blob URL  只能在當前應用內使用,把 Blob URL  複製到瀏覽器地址欄是沒法獲取數據,而 Data URL 則能夠在任意瀏覽器中使用。

6、總結

本文中咱們主要經過 4 個問題來複習了 Blob 知識點:「Blob 是什麼」、「Blob 怎麼用」、「Blob 使用場景」和「Blob 與 ArrayBuffer 區別」,在「Blob 使用場景」部分中,也主要介紹了咱們實際開發中很是常見的「圖片預覽」、「圖片下載」和「生成文件」的場景。在文章最後,也經過和你們複習了「Blob URL 和 Data URL 區別」,讓咱們對 Blob 有更深的認識。

Author 王平安
E-mail pingan8787@qq.com
博 客 www.pingan8787.com
微 信 pingan8787
每日文章推薦 https://github.com/pingan8787...
ES小冊 js.pingan8787.com
語雀知識庫 Cute-FrontEnd

bg

相關文章
相關標籤/搜索