跳到主要內容
問題研究

現在處理編碼:TextEncoder、Uint8Array 的 Base64 方法與 URLSearchParams

整理 JavaScript 現代編碼方式,使用 TextEncoder、TextDecoder 與 Uint8Array 處理 UTF-8 與 Base64,並說明 Base64URL、URLSearchParams 與 encodeURIComponent 的差異。

W

以前處理中文 Base64 時,常看到這種寫法:

btoa(unescape(encodeURIComponent('中文')))

現在不建議再使用這種方式,escape() 與 unescape() 已經 deprecated。

比較清楚的做法是先把文字轉成 UTF-8 位元組,再處理 Base64。

字串 ↔ 位元組

先記住這個流程:

字串 "中文"

  ↕ TextEncoder / TextDecoder

Uint8Array

  ↕ toBase64() / fromBase64()

Base64 "5Lit5paH"

文字轉成位元組:

const bytes = new TextEncoder().encode('中文')

// Uint8Array(6) [228, 184, 173, 230, 150, 135]

轉回文字:

new TextDecoder().decode(bytes)

// '中文'

TextEncoder 使用 UTF-8;TextDecoder 則負責將位元組重新轉回文字。

位元組 ↔ Base64

現在 Uint8Array 可以直接處理 Base64:

const bytes = new TextEncoder().encode('中文')

bytes.toBase64()
// '5Lit5paH'

Uint8Array.fromBase64('5Lit5paH')
// Uint8Array(6) [...]

完整寫法:

function encodeBase64(text) {
  return new TextEncoder().encode(text).toBase64()
}

function decodeBase64(base64) {
  return new TextDecoder().decode(
    Uint8Array.fromBase64(base64)
  )
}

encodeBase64('中文 👍')
// '5Lit5paHIPCfkY0='

decodeBase64('5Lit5paHIPCfkY0=')
// '中文 👍'

中文與 emoji 都不需要再另外處理。

Base64URL

JWT 或放在 URL 裡的 token 常使用 Base64URL。

它與一般 Base64 的主要差異是:

+ → -
/ → _

通常也會省略結尾的 =。

現在可以直接指定:

bytes.toBase64({
  alphabet: 'base64url',
  omitPadding: true
})

解碼:

Uint8Array.fromBase64(token, {
  alphabet: 'base64url'
})

Query string:URLSearchParams

Base64 與 URL encoding 是不同的東西。

如果要建立 query string,例如:

/search?q=中文&page=1

可以使用 URLSearchParams:

const params = new URLSearchParams({
  q: 'A&B 公司',
  page: '1'
})

params.toString()
// 'q=A%26B+%E5%85%AC%E5%8F%B8&page=1'

也可以直接搭配 URL:

const url = new URL('https://example.com/search')

url.searchParams.set('q', 'A&B 公司')
url.searchParams.append('tag', '前端')

url.toString()

這樣就不用自己手動拼 query string。

URLSearchParams 與 encodeURIComponent

兩者最容易注意到的差異是空白:

encodeURIComponent('a b')
// 'a%20b'

new URLSearchParams({ q: 'a b' }).toString()
// 'q=a+b'

簡單記:

  • query string → URLSearchParams
  • URL 路徑片段 → encodeURIComponent

例如:

const path = `/posts/${encodeURIComponent(slug)}`

不要把 URLSearchParams 拿來處理 URL path。

TextEncoder 與 crypto.subtle

crypto.subtle 處理的是二進位資料。

如果要計算文字的 SHA-256,需要先把字串轉成 UTF-8 位元組:

async function sha256(text) {
  const bytes = new TextEncoder().encode(text)

  const digest = await crypto.subtle.digest(
    'SHA-256',
    bytes
  )

  return new Uint8Array(digest).toHex()
}

如果原本拿到的就是 ArrayBuffer 或 Uint8Array,就不需要再經過 TextEncoder。

簡單對照

要做的事現在建議
字串 → UTF-8 位元組new TextEncoder().encode(str)
UTF-8 位元組 → 字串new TextDecoder().decode(bytes)
字串 → Base64new TextEncoder().encode(str).toBase64()
Base64 → 字串new TextDecoder().decode(Uint8Array.fromBase64(base64))
Base64URL{ alphabet: 'base64url' }
位元組 → Hexbytes.toHex()
Query stringURLSearchParams
URL 路徑片段encodeURIComponent()
escape() / unescape()不再使用

小結

現在處理文字 Base64,可以先記住這兩個方向:

// 文字 → Base64
new TextEncoder().encode(text).toBase64()

// Base64 → 文字
new TextDecoder().decode(
  Uint8Array.fromBase64(base64)
)

核心概念就是:

文字
↕
UTF-8 位元組
↕
Base64

URLSearchParams 則是用來處理 URL query string,和 Base64 是不同用途。

參考資料

分享這篇