關於 JavaScript 字串的一個小知識

李中凱發表於2020-08-14

說起字串,我們再熟悉不過了。接觸程式設計的第一個經典任務就是輸出字串:Hello, world。但是你知道 JavaScript 字串在計算機裡是怎麼表示的嗎?

最簡單直觀但不太準確的的理解就是,字串就是由英文字母、數字和標點符號等這些字元組成的序列。比如下面這個字串就是由5個字母和一個感嘆號組成的:

const message = 'Hello!';

同時也可以看出該字串的字元數是6:

const message = 'Hello!';
message.length; // => 6

如果字串是由這些可見字元(也就是 127 個 ASCII 字元) 組成的,這樣理解沒有問題。但是,一旦碰到不常見的符號,比如一些表情字元?, ?, ?,可能會得到意外的結果:

const smile = '?';
smile.length; // => 2

是不是很奇怪?明明只有一個字元,長度怎麼會是 2 呢?這是因為,JavaScript 字串實際上是由編碼單元構成的,而不是可見字元序列。

ECMA 262 規範裡是這麼描述 JavaScript 字串的:

String 型別是由零或多個 16 位無符號整數值組成的有序序列的集合。字串型別通常用於表示執行中的 ECMAScript 程式中的文字資料,在這種情況下,字串中的每個元素都被視為 UTF-16 編碼單元值。

簡單說,JavaScript 字串就是 UTF-16 編碼單元序列,一串數字而已。

一個編碼單元就是位於 0x00000xFFFF 之間的一個數字,編碼單元與字元之間有個對應關係。例如,編碼單元 0x0048 對應了實際的字元 H

const letter = '\u0048';
letter === 'H' // => true

如果把一整個字串'Hello!'用編碼單元表示就是這樣:

const message = '\u0048\u0065\u006C\u006C\u006F\u0021';
message === 'Hello!'; // => true
message.length;       // => 6

可以看到,這個字串有6個編碼單元,每個編碼單元對應一個字元。基本多文種平面 BMP(Basic Multilingual Plane)中的任意一個字元,都可以用一個 UTF-16 編碼單元表示。但是,在這個範圍以外的字元,就需要 2 個 UTF-16 編碼單元來表示了。比如前面提到的笑臉符號,編碼是\uD83D\uDE00

const smile = '\uD83D\uDE00';
smile === '?'; // => true
smile.length;  // => 2

這兩個編碼單元是成對存在的,用於表示超出 0xFFFF 的字元。不能拆開,否則就變成無法識別的亂碼了。另外,這裡的.length是2,說明這個屬性其實是字串編碼單元的個數,而不是字元數。在需要判斷字元數量的時候就要注意了,根據.length得到的結果是不準確的。那要怎麼解決呢?可以用這種辦法:

const message = 'Hello!';
const smile = '?';

[...message].length; // => 6
[...smile].length;   // => 1

部落格園不常更新,更多前端技術乾貨可前往公眾號。

相關文章