跳至主要内容

[note] Unicode 字串編碼(encode, decode)

keywords: unicode, encode, decode, escape, sequence, 代理編碼

此文章為各筆記之整理,非原創,擷取來源可見參考資料

重點總結(tl;dr)

  • 一個位元組(byte)由 8 個位元(bit)組成,每個位元都是二進制的,共可表徵 255 種狀態
  • 一個位元組可以用 2 個位數的十六進制表示
  • Unicode 的代碼點(Code Point)大多是以 U+ 作為前綴,後面放十六進位制,例如,U+0041 即表示文字符號 A
  • 0x, 0o 前綴的內容,其型態仍然是數值。
  • JS 中,看到 0x, 0o 的前輟表示為 Unicode 十六進位制和八進位制的碼點,可以透過 String.fromCodePoint() 轉換成字符,例如 0x0041
  • JS 中,看到 \x, \u, \u{} 的前輟均為 U+ 碼點的字串跳脫表示法,可以直接透過 console.log('') 輸入即會產生文字符號 ,例如 \x41\u0041
  • CSS 中,看到 \ 的前輟亦表示 Unicode 十六進位制,可以將它改成 \u<hex>console 輸入即可看到結果,例如 \0041
  • HTMLURI 中,看到以 % 作為前綴通常是將特殊字元轉成 UTF-8,可以透過 decodeURI()decodeURIComponent() 反解回來。
  • HTML 中,看到以 & 開頭,以 ; 結束,表示透過 HTML Encode 編碼過。
  • ASCII Code 的十六進位制,會對應到相同的 Unicode 十六進位制,以及相同的 UTF-16 十六進位制
  • 查詢某一文字符號的編碼可到 unicode.scarfboy.com
  • 進制轉換
// 轉成 10 進制,可以直接輸入不用使用 Number()
0xfe; // 254
Number('0xfe')(
// 254

// 10 進制轉成其他進制
254,
)
.toString(16)(
// "fe",轉成 16 進制
254,
)
.toString(2); // "11111110",轉成 2 進制

其實可以不用加上 Number() 直接輸入 0xfe 即可。

  • 碼點與文字轉換
// 根據碼點轉為文字,使用數值
String.fromCodePoint(0x41); // "A"
String.fromCodePoint(65); // "A"

// 根據碼點轉為文字:使用跳脫字符
// \x<兩位碼點>,只使用到 ASCII
// \u<四位碼點>,只使用到 Unicode 基本平面
// \u{碼點},使用到 Unicode 輔助平面
console.log('\x41'); // "A"
console.log('\u0041'); // "A"
console.log('\u{41}'); // "A"
console.log('\u{0041}'); // "A"

// 根據文字轉回碼點
const character = 'A';
character
.codePointAt()(
// 65
65,
)
.toString(16); // 0x41

進位制

進位制的表示法

keywords: bin, oct, dec, hex
進位制(縮寫)前綴法首字法下標表示法HTML 表示法
二進位(Binary;bin)0b1111111011111110b(11111110)2
八進位(Octal;oct)0376, 0o376376o(376)8
十進位(Decimal;dec)254254d(254)10
十六進位(Hexadecimal;hex)0xfefeh(fe)16&#x5a3;
  • 在 HTML ,十六進制可以用 x 表示,例如 &#x5a3;&#1443; 相同,5a3 是 16 進制,轉成 10 進制後就是 1443

在 HTML 中常用 & 開頭 ; 結尾當作一種編碼方式。

位元(bit)與位元組(byte)

keywords: bit, byte
  • 位元(bit):1 個二進制的值稱作一個位元(bit),全名是 binary digit,有 01 兩種狀態。
  • 位元組(byte):8 個二進制位元,組成一個位元組(byte),從 0000000011111111,共可表徵 255 種狀態。在中國位元組稱作字節
/**
* 1 個位元組(byte)包含 8 個位元的二進制,可以用兩位數的十六進制值表示
**/
0b11111111; // 255
Number('0b11111111'); // 255, 11111111b 等同於 255d
(255).toString(16); // 'ff'
  • 一個位元組(byte)由 8 個位元(bit)組成,每個位元都是二進制的,共可表徵 255 種狀態,並且可用 2 位數的十六進制表示
  • 留意位元位數的差別,位元均是由二進制組成,而位數是指該數值需要使用幾個位數。

使用 JS 進行進位制轉換

New number and Math features in ES6 @ 2ality

轉換成 10 進位

keywords: Number, parseInt
// 內容需要以前綴法表示
254; // 254,直接帶入就是 10 進制
0376; // 254,以舊八進制的前綴法表示
0o376; // 254,以新的八進制的前綴法表示
0xfe; // 254,以十六進制的的前綴法表示
0b11111110; // 254,以二進制的的前綴法表示
Number('0b11111110'); // 如果要用字串的話可以加上 Number()

// 指定該內容要使用的進位制
parseInt('fe', 16); // 254, 0xfe = 254
parseInt('376', 8); // 254, 0o376 = 254
parseInt('11111110', 2); // 254, 0b11111110 = 254

其實可以不用加上 Number() 直接輸入 0xf4 等等即可。

10 進位轉換成其他進位

keywords: toString
// 10 進位轉成 16 進位
(254).toString(16); // "fe",0xfe = 254

ASCII(美國資訊交換標準碼)

keywords: symbol, 文字符號

美國制定了一套字符編碼,用來定義英語字母與二進制之間的關係,這被稱為 ASCII 碼

ASCII 碼一共規定了 128 個字符的編碼,比如空格 SPACE 是 32,也就是 (00100000)2;大寫的字母 A 是 65,也就是 (01000001)2

這 128 個符號(包括 32 個不能打印出來的控制符號),只佔用了一個位元組的後面 7 位,最前面的一位統一規定為 0

在後面的文章把所有語言的單字,稱為文字符號、符號或字符(Symbol),例如英文的 a;中文的 ; 法文的 é;阿拉伯文的 Ain

Imgur

ASCII 碼是美國最早用來定義二進制文字符號(Symbol)間對應關係的表。

Unicode(萬國碼)

keywords: 碼點, 編碼位置, code point

什麼是 Unicode(U+<hex>

由於 ASCII 只能儲存最多 256 種組合,顯然對於多國語系來說相當不夠,因此出現 Unicode

Unicode 就像一個資料庫,每一個文字符號(symbol)都可以對應到一組數字

在 Unicode 中常用 U+<hex> 表示,U+ 後面的 16 進制數就叫「碼點(Code Point)」或「編碼位置」。這個編碼位置是唯一。透過這種方式可以簡單的存取特定文字符號而不用直接輸入符號本身,例如,U+0000 則碼點為 0000

此外 ASCII 的 128 個符號可以直接對應到 Unicode

`A` = `U+0041`
`a` = `U+0061`
`©` = `U+00A9`
`☃` = `U+2603`
`💩` = `U+1F4A9`

具體的符號對應表,可以在 unicode.org 中查詢 Code Charts

  • 每一個文字符號都可以對應到一個十六進制數字,這個數字就稱為 編碼位置/碼點(Code Point)
  • ASCII 可以直接對應到 Unicode。

基本平面和輔助平面(BMP and SMP)

Unicode 編碼可能的範圍從  U+0000  到  U+10FFFF  超過 110 萬個符號。

基本平面(BMP, Basic Multilingual Plane):由兩個位元組組成,是 Unicode 中最前面的 65536 個文字符號,即 (ffff)16 ,它的碼點範圍是從 0 一直到 216 - 1,寫成 16 進制就是從U+0000U+FFFF。所有最常見的字符都放在這個平面,這是 Unicode 最先定義和公佈的一個平面。

輔助平面(SMP, Supplementary planes or Astral planes):由三個位元組組成,除了基本平面外,剩下的文字符號都放在輔助平面,碼點範圍從 U+010000 一直到 U+10FFFF,如果某個字符需要超過 4 位數的 16 進制來表示那它就屬於輔助平面。

65536 是由兩個位元組組成,即 Number('0xffff')

Unicode 的問題:為什麼需要 UTF-8 或其他編碼方式

Unicode 解決的問題是將多國語言中的每一個文字符號都能夠對應到一組數字,但 Unicode 並沒有說明儲存這些數字的方式。例如「臺」的 Unicode 是 U+81FA,換成二進制的話將是 1000000111111010,一共需要 16 個位元,也就是 2 個位元組。對於其他的符號則可能需要 3 個以上的位元組。

由於在 ASCII 中用 1 個位元組來表徵符號,因此當納入 Unicode 的時候,需要:

  1. 當輸入一個以上的位元組時,電腦如何判斷它是使用 ASCII 或 Unicode?電腦如何知道是 1 個位元組來表示一個文字符號,或者 2 個、3 個位元組才表示一個文字符號?
  2. 如果統一規定用 3 個位元組來當作一個文字符號的話,那麼對於只需要 1 個位元組的英文來說,將會有 2 個位元組都是 0 ,對於空間的存儲來說造成很大的浪費。

為了解決這樣的問題,需要一種統一的編碼方式,便產生了網路上最廣泛使用的 UTF-8。

Unicode 解決的問題是將所有的文字符號都能對應到一組數字,但沒有規範該如何儲存和使用這組數字。

Unicode 的實作方式:UTF-8, UTF-16, UTF-32

UTF-8

UTF-8 是在網際網路上最廣泛被採用的一種 Unicode 的實現方式

另外仍有 UTF-16(字母用 2 個 或 4 個位元組表示)和 UTF-32(字母用 4 個位元組表示),但在網際網路上基本上不被使用。

UTF-8 只是 Unicode 的實現方式之一。

UTF-8 是變動位元組長度的一種編碼方式,不同的文字符號可能由 1 個位元組到 4 個位元組來表示,根據不同的文字符號而變化位元組的長度

UTF-8 的編碼規則很簡單,只有二條:

  1. 對於單一位元組的文字符號,位元組的第一位設為 0,後面 7 位為這個符號的 Unicode 碼。因此對於英語字母,UTF-8 編碼和 ASCII 碼是相同的
  2. 對於 n 位元組的文字符號(n > 1),第一個位元組的前 n 位都設為 1,第 n + 1 位設為 0,後面位元組的前兩位一律設為 10。剩下的沒有提及的二進制位,全部為這個符號的 Unicode 碼。

Imgur

範例一:A

舉例來說,英文字「A」的 Unicode 是 U+0041,也就是 (65)10,亦等同於 (0100 0001)2 因此根據上表顯示介於十六進制 0000 0000 ~ 0000 007F 範圍內,也就是屬於第一組。

所以「A」的 UTF-8 編碼只需要一個位元組,格式就是 0xxxxxxx。將「A」Unicode 的內容直接填入 x,即可得到「A」的 UTF-8 編碼為 0100 0001,轉換成 16 進制就是 (41)16

const character = 'A';
character
.codePointAt()(
// 65

65,
)
.toString(2)(
// 0b1000001
65,
)
.toString(16); // 0x41

範例二:臺

舉例來說,中文字「臺」的 Unicode 是 U+81FA,也就是 (33274)10,亦等同於 (1000 0001 1111 1010)2,因此根據上表顯示介於十六進制 00000800 ~ 0000FFFF 這個範圍內,也就是屬於第三組。

所以「臺」的 UTF-8 編碼需要三個位元組,格式就是 1110xxxx 10xxxxxx 10xxxxxx。然後從「臺」的最後一個二進制位數開始,依次由後往前添入格式中的 x,多出來的位數補 0

於是便可得到「臺」的 UTF-8 編碼是 11101000 10000111 10111010,轉換成十六進制就是 (E887BA)16

Imgur

臺 = U+81FA = UTF-8 Hex(E887BA) = UTF-16 Hex(81FA) = URI-encoded UTF-8 %E8%87%BA = CSS \81fa = JS \u81fa

const character = '臺';
encodeURIComponent(character); // '%E8%87%BA'
character
.codePointAt()(
// Dec(33274)
33274,
)
.toString(16); // Hex(81FA)

String.fromCodePoint(33274); // '臺'
console.log('\u81fa'); // '臺'

Number(0b11101000).toString(16); // 'e8',將 0b11101000 轉成 16 進制
Number(0b10000111).toString(16); // '87'
Number(0b10111010).toString(16); // 'ba'

範例三:严

簡體字「严」的 Unicode 是 U+4E25,也就是 (100 111000 100101)2,因此根據上表是介於十六進制 00000800 ~ 0000FFFF 這個範圍內,也就是屬於第三組。

因此「严」的 UTF-8 編碼需要三個位元組,即格式是1110xxxx 10xxxxxx 10xxxxxx。然後,從「严」的最後一個二進制位數開始,依次從後向前填入格式中的x,多出的位補0

這樣就得到了,的 UTF-8 編碼是11100100 10111000 10100101,轉換成十六進制就是E4B8A5

綜合上述, 的 Unicode 碼是U+4E25,UTF-8 編碼是 (E4B8A5)16

Imgur

严 = U+4E25 = UTF-8 Hex(E4B8A5) = UTF-16 Hex(4e25) = URI-encoded UTF-8 %E4%B8%A5 = CSS \4E25 = JS \u4E25

const character = '严';
encodeURIComponent(character); // '%E4%B8%A5'
character
.codePointAt()(
// 20005
20005,
)
.toString(16); // 0x4E25

String.fromCodePoint(20005); // '严'
console.log('\u4E25'); // '严'

UTF-32

相較於 UTF-8,UTF-32 是 Unicode 實作是最簡單的方式,每個碼點(Code Point)都使用 4 個位元組表示(也就是 8 位數的 16 進制),位元組內容一一對應碼點,不足的補 0 。

缺點是浪費過多空間(很多位元組都是 0),導致沒人使用,HTML 5 明文規定不可將網頁編碼為 UTF-32

U+0000 = 0x00000000
U+597D = 0x0000597D

每 1 個位元組,可以用 2 位數十六進制表示。

UTF-16

UTF-16 編碼介於 UTF-32 與 UTF-8 之間,同時結合了固定位元組長度和變動位元組長度這兩種編碼方法的特點。

UTF-16 的編碼規則很簡單,基本平面(BMP)用 2 個位元組(U+0000 ~ U+FFFF)表示;輔助平面(SMP)用 4 個位元組(U+01000 ~ U+10FFFF)表示。

  • 每 1 個位元組可以由 2 位數的十六進制表示。
  • UTF-16 和 Unicode 十六進位制(U+)是不同的

Unicode 基本平面的文字符號

對於 Unicode 基本平面的文字符號轉成 UTF-16 時,直接將碼點轉為對應的十六進制形式,長度為 2 個位元組即可,因次介於 U+0000U+FFFF。例如:

U+597D = UTF-16(0x597D)

對於 Unicode 基本平面的文字符號來說,Unicode 的值會直接等同於 UTF-16 的值。

Unicode 輔助平面的文字符號

由於 Unicode 輔助平面的文字符號超過兩個位元組,因此需要判斷這兩個位元組到底是屬於一個文字符號,還是兩個文字符號。

由於 Unicode 基本符號從 U+D800U+DFFF 是一個空段,因此在 UTF-16 中,把一個輔助平面的文字符號,可以拆成兩個基本平面中空段的文字符號來表示,也就是把輔助平面映射到基本平面上。其中一半映射到 0xD800 ~ 0xDBFF ;另一半則映射到 0xDC00 ~ 0xDEFF 上。

輔助平面的字符(U+010000U+10FFFF )在 UTF-16 中佔用 4 個位元組:

// U+1D306 轉換為 UTF-16 後為 0xD834 0xDF06

H = Math.floor((0x1d306 - 0x10000) / 0x400) + 0xd800 = 0xd834;
L = ((0x1d306 - 0x10000) % 0x400) + 0xdc00 = 0xdf06;

在 JavaScript 中使用 Unicode

JavaScript 最早用的是 UCS-2 ,因為當時仍未出現 UTF-16 的編碼。這導致所有超過兩個位元組的文字符號在編碼時可能出現錯誤,例如 4 個位元組表示的文字符號,會被拆成 2 個雙位元組的文字符號處理。

後來 UTF-16 取代了 UCS-2 ,或者說 UCS-2 整合進了 UTF-16。所以現在只有 UTF-16,沒有 UCS-2。因此在目前 JavaScript 中,主要是支援 UTF-16,但在操作時顯示的都是 Unicode 的編碼位置

對於 Unicode 輔助平面的文字符號,使用傳統的 JavaScript 函式可能會產生錯誤的結果。

於字串中使用 JavaScript 跳脫字元直接輸出

在 JavaScript 中有幾種不同的方式可以使用 Unicode:

只使用到 ASCII 字符時 (\\x)

  • 使用:\x<碼點>
  • 適用範圍: U+0000U+00FF,只用到 Unicode 兩位數的十六進制時,也就是一個位元組時
  • 舉例來說, U+0041 = \x41
console.log('\x41\x42\x43'); // 'ABC'

在 HTML,十六進制可以用「x」hex,例如字母 「A」 (U+0041) 可以表示為 \u0041\x41

只使用到 Unicode 基本平面字符時(\\u)

  • 使用:\u<碼點>
  • 適用範圍:Unicode 基本平面字符,也就是 U+0000U+FFFF
'好' === '\u597D'; // true
console.log('\u0041'); // 'A'
console.log('\u0041\u0042\u0043'); // 'ABC'
console.log('I \u2661 JavaScript'); // 'I ♡ JavaScript'

使用到 Unicode 輔助平面字符時(\u{碼點}

  • 使用:\u{碼點}
  • 適用範圍:超過 U+FFFF 的字元

對於超過 U+FFFF 的字元,在 ES6 中引進了新類型的跳脫序列 Unicode code point escapes ,透過將碼點放在大括號 {},也就是 \u{碼點} 就能正確識別:

// 使用 2 位數的十六進制(一個位元組)
console.log('\u{41}\u{42}\u{43}'); // 'ABC'

// 使用 4 位數的十六進制(兩個位元組)
console.log('\u{0041}\u{0042}\u{0043}') // ABC

// 使用 4 位數以上的十六進制(兩個以上的位元組)
console.log('\u{1F4A9}'); // '💩' U+1F4A9
console.log('\u{1D306}') // 𝌆

統整如下:A = U+0041 = \x41 = \u0041 = \u{41} = \u{0041}

下面這幾種寫法都會得到相同的結果:

console.log('\x41'); // 'A'
console.log('\u0041'); // 'A'
console.log('\u{41}'); // 'A'
console.log('\u{0041}'); // 'A'

JavaScript 中的相關函式

由於在 ES5 前 JavaScript 沒辦法處理具有四個位元組的字符,因此在 ES6 中多出了 String.fromCodePoint(), String.prototype.codePointAt()String.prototype.at() 這幾個函式:

// Unicode 碼點 -> 文字符號
String.fromCodePoint(<num1> [, ...[, numN]]) // ES6

// 文字符號 --> Unicode 碼點(10 進位)
String.prototype.codePointAt(<index>) // ES6

// 返回字串特定位置的文字符號
String.prototype.at(<index>) // ES7
String.prototype.charAt(<index>) // 輔助字符會有錯誤

建議在支援 ES6 的情況下,使用新的函式以避免錯誤

文字符號 --> Unicode 碼點(回傳十進位)

keywords: String.prototype.codePointAt(<index>), String.prototype.charCodeAt()

使用 String.prototype.codePointAt(<index>) 可以將文字符號轉成 Unicode,回傳的結果是十進制,其中 index 指的是該字串的第幾個字:

/**
* 由於 String.prototype.codePointAt() 會回傳的是 10 進位制,
* 因此,我們可以透過 Number.prototype.toString(16) 將 10 進位制轉成 16 進位制。
**/

'A'
.codePointAt()(
// 65
65,
)
.toString(16); // Hex(41),即表示 U+0041

'ABC'.codePointAt(2); // 67
// 一行寫完
'國'.codePointAt().toString(16);

當使用的是輔助平面字符(需 UTF-16 的四個位元組)時,需使用新的函式 String.prototype.codePointAt() 才能得到正確的結果:

// 取得 Unicode 碼點(10 進位)
'💩'.codePointAt(); // 128169
'💩'.charCodeAt(); // 錯誤:55357

// 10 進位轉成 16 進位
(128169).toString(16); // '1f4a9', Dec(128169) = Hex(1f4a9) = U+1f4a9
(55357).toString(16); // 'd83d', Dec(55357) = Hex(d83d)

// 輸入 Unicode 取得文字字符
console.log('\u{1f4a9}'); // 正確輸出 '💩'
console.log('\u{d83d}'); // 錯誤輸出 �

Unicode 碼點 --> 文字符號

keywords: String.fromCodePoint<num>, String.fromCharCode(<num>)

或者透過 String.fromCodePoint(number) 也可以將 Unicode 碼點轉換成文字符號。

num 前輟為:

  • 0x ,為十六進位表示法(hex)
  • 0o,為八進位表示法(oct)
  • 沒有前輟,為十進位表示法(dec)
/**
* 當只是使用 Unicode 中的基本平面時(只佔兩個位元組),
* 使用 fromCharCode 和 fromCodePoint 得到的結果相同
**/
// 65d = 41h = 101o
String.fromCodePoint(65); // A,使用 10 進位
String.fromCodePoint(0x0041); // A,使用 16 進位
String.fromCodePoint(0o101); // A,使用 8 進位
/**
* 當使用的是 Unicode 中的輔助平面時(佔四個位元組),
* 使用 fromCodePoint 才能得到正確的結果
**/
String.fromCodePoint(0x1f4a9); // 正確:💩
String.fromCharCode(0x1f4a9); // 錯誤:�

正確顯字符長度

由於 ES6 之前尚無法使用 \u{} 這種 Unicode code point escapes 的方式來輸入輔助平面的字元,因此在 ES6 之前會使用成對編碼的方式來處理,但這使得過去使用 String.prototype.length 的方式可能無法正確判斷字符長度:

'💩' == '\uD83D\uDCA9'      // true
'💩'.length // 錯誤:2(誤以為是兩個字符)
Array.from('💩').length // 正確:1
[...'💩'].length // 正確:1

錯誤判斷字符長度的情況僅會發生在「輔助平面(BMP)」的情況,也就是無法用 4 個位元的 16 進制表示時。

特殊字元

  • \b: backspace (U+0008 BACKSPACE)
  • \f: form feed (U+000C FORM FEED)
  • \n: line feed (U+000A LINE FEED)
  • \r: carriage return (U+000D CARRIAGE RETURN)
  • \t: horizontal tab (U+0009 CHARACTER TABULATION)
  • \v: vertical tab (U+000B LINE TABULATION)
  • \0: null character (U+0000 NULL) (only if the next character is not a decimal digit; else it’s an octal escape sequence)
  • \': single quote (U+0027 APOSTROPHE)
  • \": double quote (U+0022 QUOTATION MARK)
  • \\: backslash (U+005C REVERSE SOLIDUS)

CSS 與 Unicode(\<碼點>

如果想要在 CSS 中使用 Unicode,可以使用前輟 \<hex> 來進行跳脫:

.content {
display: inline-block;
background-color: steelblue;
padding: 100px;
color: white;
&::before {
content: '\0041'; // A, 輸入 16 進位制的 Unicode
// content: '\570B'; // 國
// content: '\00A9'; // ©
}
}

HTML 頁面中的特殊字元(&#<碼點>;

在 HTML 中有些特殊字元如果要以文字呈現,可以使用 HTML Encode 將這些特殊字元轉換成 HTML entity 。這些 HTML entity 會以 & 開頭,以 ; 結束,若為十六進制的值則前綴加上 x 表示。

&; 之間,可能放的是語意話的文字(&colon;),或者是十六進位制(&#x0003A;),或者是十進位制(&#58;):

"&" = "&amp;" = "&AMP;" = "&#x00026;" = "&#38;"

HTML 字符對照表

網址/頁(URI)中的特殊字符(%)

文字符號 --> U+碼點(%, %u)

傳統上會使用 JavaScript 的 escape()unescape() 將網頁或網址中的特殊字元轉換成 Unicode 十六進制碼點。透過此編碼的字符會是% 作為前輟的 Unicode 十六進位制,可能是 %xx%uxxxx

// 對於 ASCII 這類只需要一個位元組的文字符號(U+0000 ~ U+00FF),會使用 '%xx`
escape('"'); // '%22'

// 對於比 U+0xFF 更大的字符,會使用 '%uxxxx'
escape('國'); // '%u570B'
escape('💩'); // '%uD83D%uDCA9'
'💩' == '\uD83D\uDCA9'; // true
unescape('%uD83D%uDCA9'); // '💩'

escape()unescape() 未來將被捨棄。 MDN 建議使用: encodeURI(), decodeURI(), encodeURIComponent(), decodeURIComponent()

轉成 UTF-8(%)

keywords: encodeURI(), decodeURI(), encodeURIComponent(), decodeURIComponent()

在 HTML 的規範中,網頁不可以用 UTF-16,而多數是用 UTF-8。透過 encodeURI()encodeURIComponent() 可以將字串轉成 UTF-8。

這兩個函式的差別在於,encodeURI() 不會對 URI 具有特殊意義的字符編碼(例如,ASCII 碼、數字、- _ . ! ~ * ' ( ) ;/?:@&=+$,#),但 encodeURIComponent() 則是全部都會進行編碼,編碼完的內容會是 UTF-8

encodeURI('/'); //   '/' 是 URI 中有意義的字符,不會進行編碼
encodeURIComponent('/'); // '%2F'

encodeURI('&'); // '&' 是 URI 中有意義的字符,不會進行編碼
encodeURIComponent('&'); // '%26'

encodeURIComponent('國'); // '%E5%9C%8B'
encodeURIComponent('💩'); // '%F0%9F%92%A9'

Base 64 編碼

Base64 主要是用來將二進位制的檔案轉換成 ASCII ,因為當有二進位制的資料需要透過網路傳遞時,為了要解決一些控制字元無法傳遞的問題,人們會透過 Base64 來編碼這些二進位制的資料。

一樣可以透過 JavaScript 來進行 Base64 的編碼和解碼:

// 瀏覽器環境下
btoa('Hello World'); // 編碼,"SGVsbG8gV29ybGQ="
atob('SGVsbG8gV29ybGQ='); // 解碼,"Hello World"

// Node.js
Buffer.from('Hello World').toString('base64'); // 編碼
Buffer.from('SGVsbG8gV29ybGQ=', 'base64').toString('ascii'); //解碼

也可以在終端機直接 encode 字串成 base64:

# 一定要加上 "-n",否則 base64 出來的字串會包含換行符號
$ echo -n "foo" | base64 # Zm9v
$ echo -n "Zm9v" | base64 --decode

工具

參考資料

ASCII, Unicode, UTF-8, UTF-16, JavaScript

進階閱讀