【筆記】618- 讀《你不知道的 Blob》筆記

往期讀書筆記:

1.【筆記】607- 讀《你不知道的 WeakMap》筆記


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

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

  1. Blob 是什麼?

  2. Blob 怎麼用?

  3. Blob 有哪些使用場景?

  4. Blob 與 ArrayBuffer 有何區別?

一、Blob 是什麼?

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

二、Blob 怎麼用?

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

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

1. 構造函數

Blob 構造函數語法爲:

const myBlob = new Blob(blobParts[, options])

入參:

  • blobParts:它是一個由 ArrayBuffer,ArrayBufferView,Blob,DOMString 等對象構成的數組。DOMStrings 會被編碼爲 UTF-8。

  • options :一個可選的對象,包含以下兩個屬性:

    • type :默認值爲 "" ,表示將會被放入到 blob 中的數組內容的 MIME 類型。

    • endings :默認值爲 "transparent",用於指定包含行結束符 \n 的字符串如何被寫入。它是以下兩個值中的一個:"native",代表行結束符會被更改爲適合宿主操作系統文件系統的換行符,或者 "transparent",代表會保持 blob 中保存的結束符不變。

出參:
返回一個新創建的 Blob 對象,其內容由參數中給定的數組串聯組成。

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 字符串:我們無法更改字符串中的字符,但可以創建新的更正後的字符串。

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

三、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 兩種方式實現圖片本地預覽,這兩個類型的區別在**《五、拓展》**中介紹。

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

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

五、拓展

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

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 則可以在任意瀏覽器中使用。

六、總結

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

1. JavaScript 重溫系列(22篇全)

2. ECMAScript 重溫系列(10篇全)

3. JavaScript設計模式 重溫系列(9篇全)

4. 正則 / 框架 / 算法等 重溫系列(16篇全)

5. Webpack4 入門(上)|| Webpack4 入門(下)

6. MobX 入門(上) ||  MobX 入門(下)

7. 59篇原創系列彙總

回覆“加羣”與大佬們一起交流學習~

點擊“閱讀原文”查看70+篇原創文章

點這,與大家一起分享本文吧~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章