前端开发中的二进制与编码

318 阅读5分钟

博客原链接messiahhh.github.io/blog/docs/n…

Buffer

Buffer用于表示固定长度的字节序列,Buffer仅存在于Node.js环境中在浏览器环境中使用ArrayBuffer作为替代,二者的原理基本一致但API层面存在些许差异。

通过fs模块不指定编码方式encoding读取文件时,我们能够获取到表示二进制数据的Buffer对象。

Buffer.alloc()

可以用来生成指定长度的Buffer,也可以同时指定Buffer字节序列的值。

const buffer = Buffer.alloc(10) // <Buffer 00 00 00 00 00 00 00 00 00 00>
const buffer = Buffer.alloc(10, 0xfc) // <Buffer fc fc fc fc fc fc fc fc fc fc>

Buffer.from(string[, encoding])

将字符串编码成Buffer表示的字节序列,默认为utf-8编码

const buffer = Buffer.from('aka') // <Buffer 61 6b 61>  
const buffer = Buffer.from('你好') // <Buffer e4 bd a0 e5 a5 bd>

buffer.toString([encoding])

Buffer解码成字符串,默认为utf-8编码

const buffer = Buffer.from('aka') 
const str = buffer.toString() // aka

ArrayBuffer

浏览器中的ArrayBuffer对应Node.js环境中的Buffer,都表示内存中的一块字节序列,但在浏览器中我们不能直接操作这块内存,而是必须通过Typed Array来操作,常见的Typed ArrayInt8ArrayUint8ArrayInt16Array等等。

类型化数组(Typed Array)

new Uint8Array([100, 200, 300])

DataView

TODO

String and Encoding

无论是Buffer还是ArrayBuffer,本质上都表示着内存中的二进制数据(或者叫字节序列),通过某种编码方式我们就能获取其对应的字符串。

常见的编码方式有asciilatin1utf-8utf-16gbk等等,需要注意平常所说的Unicode指的是一种字符集(字符与码点的映射),utf-8utf-16utf-32才是具体的编码方式。

:::tip

ascii编码的有效位数为7位,无法表示某些西欧字符,latin-1使用了ascii中没有直接用到的最高位来兼容ascii的同时表达更多的字符。

:::

如我们在Buffer一节中所介绍的,在Node.js环境中可以通过Buffer.from()buffer.toString()实现Buffer和字符串的转化(即二进制数据和字符串的编码和解码)。而在浏览器中并不存在Buffer变量,为了实现ArrayBuffer和字符串的转化,我们需要借助TextEncoderTextDecoder来实现。

TextEncoderTextDecoder可以用来实现TypedArray和字符串的转化,而TypedArray可以通过.buffer属性访问对应的ArrayBuffer对象,所以间接地实现了我们的需求。

TextEncoder

const arr = new TextEncoder().encode('akara') // Uint8Array(5) [97, 107, 97, 114, 97, buffer: ArrayBuffer(5)]
const buffer = arr.buffer // ArrayBuffer(5)

TextDecoder

const arr = new Uint8Array([97, 107, 97, 114, 97])
new TextDecoder().decode(arr) // akara

:::caution

需要注意的是,为了遵循规范TextEncoderTextDecoder只支持utf-8编码。

:::

encodeURI

encodeURI('你') // '%E4%BD%A0'

const buffer = Buffer.from('你')
console.log(buffer) // <Buffer e4 bd a0>

decodeURI

decodeURI('%E4%BD%A0') // 你

Base64

Base64也是一种编码方式,可以实现二进制字节序列和字符串的转化。

给定一个字节序列,每三个字节作为一大组共有24个比特,将这24个比特分为四个小组,则每个小组包含6个比特,在每个小组前面加上2个比特00。那么原本3个字节的数据就变成了4个字节的数据,现在每个字节的有效位数为6位,此时每个字节可以映射为64种不同的字符,因此这种编码方式被叫做Base64

但在实际生活中,我们会经常看到Base64来用来对字符串混淆(伪加密),明明这是一种针对二进制数据的编码方式?比如window.btoa,这是因为内部会先通过latin-1将字符串编码成二进制,再进行Base64编码,编码后的数据比原本增大了三分之一。

Base64的一个常用途径是用来将小型图片进行编码,通过<img src="data:img/gif;base64,base64,xxx" />形式进行图片的加载,从而减少不必要的网络请求。

Blob

Blob只存在于浏览器环境中,可以大致把它视为一个类文件对象,是后续将介绍的File对象的父类。

fetch()
.then(res => res.blob())
.then(blob => {
    console.log(blob)
})

new Blob()

可用于根据ArrayBuffer或字符串生成Blob

new Blob([JSON.stringify({
    name: 'akara'
})], { type: 'application/json' })
new Blob([new Uint8Array([10, 20]).buffer], {})

blob.arrayBuffer()

Blob转化为ArrayBuffer,需要注意函数的返回值为Promise

blob.text()

Blob转化为字符串,编码为utf-8,需要注意函数的返回值为Promise

blob.text().then(text => console.log(text))

blob.slice()

将文件或blob分割成多个blob,常用于大型图片的上传。

URL.createObjectURL(blob)

当后端将读取的文件作为响应体发送给前端时,我们常见的需求有:①下载文件到本地。②在本地显示图片。

这种需求的关键在于根据给定的文件或blob生成一个URL路径,将其放入aimg标签的属性中。

const url = URL.createObjectURL(blob)
a.href = url // 结构类似于 blob:http://localhost:3000/486ef892-d4fc-485f-b4ab-fae272d35e55
a.download = '下载文件.txt' // 文件名

const url2 = URL.createObjectURL(blob)
img.src = url2

File

FileBlob的子类,通常进行文件上传时可以拿到File对象。

const el = document.querySelector('input')
console.log(el.files) // FileList
el.files[0] // File

闭环转换

通过上述几节我们了解到ArrayBufferStringBlob的基本情况,并且知道它们直接是可以进行闭环任意转化的:

  1. String -> Uint8Array(TextEncoder)
  2. Uint8Array -> String(TextDecoder)
  3. Uint8Array -> ArrayBuffer(.buffer)
  4. ArrayBuffer -> Uint8Array(new Uint8Array)
  5. ArrayBuffer -> Blob(new Blob)
  6. Blob -> ArrayBuffer(arrayBuffer)
  7. Blob -> String(text)
  8. String -> Blob(new Blob)

FileReader

FileReader用于读取文件中的数据,Blob内置了一些方法来获取对应的ArrayBuffer、字符串,而FileReader也提供了类似的功能。

const reader = new FileReader()
reader.onload = function() {
    console.log(reader.result)
}
reader.readAsXXX(blob)

readAsArrayBuffer()

功能类似于blob.arrayBuffer()

readAsText()

功能类似于blob.text()

readAsDataURL()

功能类似于URL.createObjectURL,但稍微有些不同。

这个方式是将文件内容的字节序列通过Base64编码得到字符串,即以data:application/octet-stream;base64,开头的长URL;而URL.createObjectURL()实际上拿到的是一个以blob:http://xxx.com/xxx开头的短URL,这个URL将会指向内存中的对应地址。

readAsBinaryString()

感觉是通过类似latin-1来进行逐字节的字符串编码。