有趣的 JavaScript 格式轉換

原文連接:https://ssshooter.com/2019-04...javascript

以前作文件上傳和 canvas 修圖時接觸到幾個格式,這裏打算整理一下他們的關係html

Blob

<input type="file" id="avatar" name="avatar" accept="image/png, image/jpeg" />

使用 input 獲取文件時,你拿到的就是 file 對象,而 file 繼承於 blob,因此直接講比較陌生的 blob 吧。java

BLOB (binary large object),二進制大對象,是一個能夠存儲二進制文件的「容器」。git

Blob 有什麼用

使用 Blob 可讓你在瀏覽器生成一個臨時文件,使用 URL.createObjectURL() 獲取他的連接,你就能像服務器文件同樣使用他。es6

let temp = new Blob(['hello fantasy'])
URL.createObjectURL(temp)
// 返回 "blob:https://ssshooter.com/1bf84bba-b53a-4155-8348-33a487e8ab7e"

返回的 url 前面是主機,後面是一個惟一識別碼。github

在建立這個臨時文件後,只要不關閉當前頁面,這個文件就會一直存在於內存,你須要主動運行 URL.revokeObjectURL(url) 刪除引用。算法

從 Blob 中提取數據

在控制檯打出 blob 你根本不知道里面是啥,那麼怎麼讀取 blob 呢?canvas

藉助 FileReader,你能夠把 Blob 讀取爲 Buffer。數組

FileReader 對象容許 Web 應用程序異步讀取存儲在用戶計算機上的文件(或原始數據緩衝區)的內容,使用 File 或 Blob 對象指定要讀取的文件或數據。
var reader = new FileReader()
reader.addEventListener('loadend', function() {
  // reader.result 包含轉化爲類型數組的blob
})
reader.readAsArrayBuffer(blob)

ArrayBuffer

通過 FileReader 的讀取,你能看到計算機儲存數據的本質 —— 二進制數據。瀏覽器

ArrayBuffer 的編輯

ArrayBuffer 相似數組,每一格放入 1Byte(8bit)數據,也就是八位的 0 或 1,因此換成十進制一格最大是 255.

例如:

newbuffer.png

ArrayBuffer 不能直接操做,而是要經過類型數組對象(下面列出來的)或 DataView 對象來操做,它們會將緩衝區中的數據表示爲特定的格式,並經過這些格式來讀寫緩衝區的內容。

  • Int8Array:8 位有符號整數,長度 1 個字節,溢出處理爲 數值%(取模)255。
  • Uint8Array:8 位無符號整數,長度 1 個字節。
  • Uint8ClampedArray:8 位無符號整數,長度 1 個字節,溢出處理爲 255(最大值)。
  • Int16Array:16 位有符號整數,長度 2 個字節。
  • Uint16Array:16 位無符號整數,長度 2 個字節。
  • Int32Array:32 位有符號整數,長度 4 個字節。
  • Uint32Array:32 位無符號整數,長度 4 個字節。
  • Float32Array:32 位浮點數,長度 4 個字節。
  • Float64Array:64 位浮點數,長度 8 個字節。

對應視圖的轉換還很神奇的,ArrayBuffer 是一個格子 8 位,也就是跟 xx8Array 是同樣的,天然不用轉換,而 16 以後的都是把每幾個格子合成一個。

// 建立 buffer
ab = new ArrayBuffer(4)
// 建立視圖
a = new Uint8Array(ab)
// 經過視圖操做 buffer
a[0] = 2
a[1] = 25
a[2] = 31
a[2] = 233

打印 ab 會輸出:

buffer.png

爲了方便理解他們如何轉換,我把他轉爲 2 進制:

// Int16Array
;['1100100000010', '11101001']
// Int32Array
;['111010010001100100000010']
// Uint8Array
;['10', '11001', '11101001', '0']

可能還沒能看出來?那再在前面補 0:

// Int16Array
;['0001100100000010', '0000000011101001']
// Int32Array
;['00000000111010010001100100000010']
// Uint8Array
;['00000010', '00011001', '11101001', '00000000']

這就很明顯能看出:要轉換就要在分組以後把同一組數據從右到左拼接。

不過我自覺通常不太會用到這麼細緻的 bit 操做(

canvas 與 buffer

另外,canvas 能夠經過 ctx.createImageData() 獲得 ImageData

ImageData.data 就是一個 Uint8ClampedArray,裏面順序放着圖片每個像素的 rgba 值。你能夠對這個 Uint8ClampedArray 進行一系列操做,再用 canvas.toBlob 這個 buffer 變回 Blob,就完成了圖片編輯的操做。

經過這個操做,再結合一些卷積核相關知識,就能完成相似這個卷積核圖片修改器的功能。

Data Url

Data Url 是一個前綴爲 data:協議,你能夠藉助這個協議在文檔中嵌入一些小文件(最多見就是內聯圖片了),數據格式以下:

data:[<mediatype>][;base64],<data>

mediatype 填入MIME 類型,MIME 也用於服務器返回數據時指定數據類型;base64 是一種編碼方式;後面接着就是數據本體。

幾個例子:

  • 普通文字:data:,Hello%2C%20World!
  • base64 處理的文字:data:text/plain;base64,SGVsbG8sIFdvcmxkIQ%3D%3D
  • html 文檔:data:text/html,%3Ch1%3EHello%2C%20World!%3C%2Fh1%3E
  • 執行 script 的 html 文檔:data:text/html,<script>alert('hi');</script>

base64

上面提到的 base64 不算是一種加密算法,它只是簡單地將每 3 個 8bit 字符轉換爲 4 個 6Bit 字符(base64 只有 2^6 = 64 種字符,所以得名),這樣保證了傳輸中一定使用 ASCII 中可見字符,不會出奇怪的空白字符或是功能性標誌 。

因爲是 3 個字符變 4 個,那麼很明顯了,base64 編碼後,編碼對象的體積會變成原來的 4/3 倍

特別要注意的是若是 bit 數不能被 3 整除,須要在末尾添加 1 或 2 個 byte(8 或 16bit),而且末尾的 0 不使用 A 而使用 =,這就是爲何 base64 有的編碼結果後面會有一或兩個等號。

喜聞樂見的舉例時間:

前置知識點:utf8 與 unicode 的關係

你可使用 charCodeAt() 獲取一個字符的 unicode 編碼(0 到 65535 之間的整數),可是 unicode 只是一個碼錶,並非一個具體的編碼方式,utf8 纔是。因此你拿到的英文字母編碼先後同樣的,可是漢字(等字符)就不一樣了。

爲了直接獲得漢字的 utf8 碼,使用 TextEncoder(其實還能夠選擇使用 encodeURIComponent 處理漢字,可是英文又不能正常轉換了)。

下面用 JavaScript 簡單寫個 base64 轉換流程:

var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
Array.from(new TextEncoder().encode('碧藍幻想'))
  .map(val => val.toString(2).padStart(8, 0)) // 轉二進制,補充到八位
  .join('')
  .replace(/([10]{6})/g, '$1,') // 每六位插個逗號用於拆分
  .split(',') // 拆分爲每 6 位一組
  .map(val => parseInt(val.padEnd(6, 0), 2)) // 補充後面的 0(可是沒有補夠)
  .map(val => b64[val])
  .join('')

結果基本是對的,可是後面的 = 仍是用了普通的 A 並且位數沒有加夠,因此上面轉換出來的不是標準 base64,僅供參考

base64 算法參考

blob url 與 Data Url 對比

blob url

  • 不須要作編碼,省了運算資源
  • 大小也不會改變
  • 在不使用時須要手動刪除引用
  • 關閉頁面連接自動廢棄

Data Url

  • 須要編碼,且體積變大 1.3 倍
  • 容易刪除
  • 連接不變,保存了能夠之後使用

參考

mdn Blob

mdn FileReader

mdn HTMLCanvasElement

阮一峯 ES6 arraybuffer

進階 FileAPI 實現標準

javascript info - blob

附錄

N 進制數的表示方法

// 10
var a = 10
// 8
var b = 0o1234567
// 或直接在前面加0,若是後面數字都小於8就自動變成8進制
var c = 01234567
// 2
var d = 0b1010101110101
// 16
var e = 0xe87a90

N 進制數轉 10 進制

parseInt(0o1234567, 8)
parseInt(0b1010101110101, 2)
parseInt(0xe87a90, 16)

10 進制數轉 N 進制

;(123456).toString(2)
;(123456).toString(8)
;(123456).toString(16)
相關文章
相關標籤/搜索