JavaScript 給急躁的程式設計師 (ES2022 版)
請支持這本書:購買捐款
(廣告,請不要封鎖。)

20 字串



20.1 秘笈:字串

字串是 JavaScript 中的基本值,且不可變。也就是說,與字串相關的操作總是會產生新的字串,而不會變更現有的字串。

20.1.1 使用字串

字串的字面值

const str1 = 'Don\'t say "goodbye"'; // string literal
const str2 = "Don't say \"goodbye\""; // string literals
assert.equal(
  `As easy as ${123}!`, // template literal
  'As easy as 123!',
);

反斜線用於

String.raw 標記模板(第 A 行)中,反斜線被視為一般字元

assert.equal(
  String.raw`\ \n\t`, // (A)
  '\\ \\n\\t',
);

將值轉換為字串

> String(undefined)
'undefined'
> String(null)
'null'
> String(123.45)
'123.45'
> String(true)
'true'

複製字串的部分

// There is no type for characters;
// reading characters produces strings:
const str3 = 'abc';
assert.equal(
  str3[2], 'c' // no negative indices allowed
);
assert.equal(
  str3.at(-1), 'c' // negative indices allowed
);

// Copying more than one character:
assert.equal(
  'abc'.slice(0, 2), 'ab'
);

串接字串

assert.equal(
  'I bought ' + 3 + ' apples',
  'I bought 3 apples',
);

let str = '';
str += 'I bought ';
str += 3;
str += ' apples';
assert.equal(
  str, 'I bought 3 apples',
);

20.1.2 JavaScript 字元與代碼點與字形群集

JavaScript 字元大小為 16 位元。它們是字串中編入索引的內容,也是 .length 計算的內容。

代碼點是 Unicode 文字的原子部分。它們大多符合一個 JavaScript 字元,有些則佔用兩個(特別是表情符號)

assert.equal(
  'A'.length, 1
);
assert.equal(
  '🙂'.length, 2
);

字形群集使用者感知的字元)代表書寫符號。每個字形群集包含一個或多個代碼點。

由於這些事實,我們不應該將文字分割成 JavaScript 字元,我們應該將它分割成字形群集。有關如何處理文字的更多資訊,請參閱 §20.7「文字的原子:代碼點、JavaScript 字元、字形群集」

20.1.3 字串方法

此小節簡要概述字串 API。本章節的結尾有一個 更全面的快速參考

尋找子字串

> 'abca'.includes('a')
true
> 'abca'.startsWith('ab')
true
> 'abca'.endsWith('ca')
true

> 'abca'.indexOf('a')
0
> 'abca'.lastIndexOf('a')
3

分割和合併

assert.deepEqual(
  'a, b,c'.split(/, ?/),
  ['a', 'b', 'c']
);
assert.equal(
  ['a', 'b', 'c'].join(', '),
  'a, b, c'
);

填補和修剪

> '7'.padStart(3, '0')
'007'
> 'yes'.padEnd(6, '!')
'yes!!!'

> '\t abc\n '.trim()
'abc'
> '\t abc\n '.trimStart()
'abc\n '
> '\t abc\n '.trimEnd()
'\t abc'

重複和變更大小寫

> '*'.repeat(5)
'*****'
> '= b2b ='.toUpperCase()
'= B2B ='
> 'ΑΒΓ'.toLowerCase()
'αβγ'

20.2 一般字串文字

一般字串文字以單引號或雙引號為界

const str1 = 'abc';
const str2 = "abc";
assert.equal(str1, str2);

單引號使用較為頻繁,因為這樣可以更容易地提及 HTML,其中較常使用雙引號。

下一章涵蓋模板文字,它提供我們

20.2.1 跳脫

反斜線讓我們可以建立特殊字元

反斜線也讓我們可以在字串文字中使用字串文字的分隔符號

assert.equal(
  'She said: "Let\'s go!"',
  "She said: \"Let's go!\"");

20.3 存取 JavaScript 字元

JavaScript 沒有額外的資料類型來表示字元,字元總是表示為字串。

const str = 'abc';

// Reading a JavaScript character at a given index
assert.equal(str[1], 'b');

// Counting the JavaScript characters in a string:
assert.equal(str.length, 3);

我們在螢幕上看到的字元稱為字形群集。它們大多數由單一 JavaScript 字元表示。但是,也有由多個 JavaScript 字元表示的字形群集(特別是表情符號)

> '🙂'.length
2

其運作方式說明於 §20.7 「文字的原子:碼點、JavaScript 字元、字位群集」

20.4 透過 + 進行字串串接

如果至少一個運算元是字串,加號運算子 (+) 會將任何非字串轉換為字串,並串接結果

assert.equal(3 + ' times ' + 4, '3 times 4');

如果我們想要逐一組裝字串,賦值運算子 += 會很有用

let str = ''; // must be `let`!
str += 'Say it';
str += ' one more';
str += ' time';

assert.equal(str, 'Say it one more time');

  透過 + 進行串接很有效率

使用 + 組裝字串相當有效率,因為大多數 JavaScript 引擎會在內部最佳化它。

  練習:串接字串

exercises/strings/concat_string_array_test.mjs

20.5 轉換為字串

以下有轉換值 x 為字串的三種方式

建議:使用具描述性且安全的 String()

範例

assert.equal(String(undefined), 'undefined');
assert.equal(String(null), 'null');

assert.equal(String(false), 'false');
assert.equal(String(true), 'true');

assert.equal(String(123.45), '123.45');

布林值的陷阱:如果我們透過 String() 將布林值轉換為字串,通常無法透過 Boolean() 將其轉換回來

> String(false)
'false'
> Boolean('false')
true

Boolean() 會傳回 false 的唯一字串是空字串。

20.5.1 將物件字串化

純粹的物件有一個預設的字串表示法,但不太實用

> String({a: 1})
'[object Object]'

陣列有更好的字串表示法,但它仍然隱藏了許多資訊

> String(['a', 'b'])
'a,b'
> String(['a', ['b']])
'a,b'

> String([1, 2])
'1,2'
> String(['1', '2'])
'1,2'

> String([true])
'true'
> String(['true'])
'true'
> String(true)
'true'

將函式字串化會傳回其原始碼

> String(function f() {return 4})
'function f() {return 4}'

20.5.2 自訂物件的字串化

我們可以透過實作 toString() 方法來覆寫內建的物件字串化方式

const obj = {
  toString() {
    return 'hello';
  }
};

assert.equal(String(obj), 'hello');

20.5.3 字串化值的替代方式

JSON 資料格式是 JavaScript 值的文字表示法。因此,JSON.stringify() 也可用於將值轉換為字串

> JSON.stringify({a: 1})
'{"a":1}'
> JSON.stringify(['a', ['b']])
'["a",["b"]]'

但 JSON 僅支援 null、布林值、數字、字串、陣列和物件(它總是將物件視為由物件文字所建立的)。

提示:第三個參數讓我們可以切換多行輸出,並指定要縮排多少,例如

console.log(JSON.stringify({first: 'Jane', last: 'Doe'}, null, 2));

此陳述式會產生以下輸出

{
  "first": "Jane",
  "last": "Doe"
}

20.6 比較字串

字串可以使用以下運算子進行比較

< <= > >=

有一個重要的注意事項:這些運算子會根據 JavaScript 字元的數字值進行比較。這表示 JavaScript 使用的字串順序與字典和電話簿中使用的順序不同

> 'A' < 'B' // ok
true
> 'a' < 'B' // not ok
false
> 'ä' < 'b' // not ok
false

正確比較文字超出了本書的範圍。它透過 ECMAScript 國際化 API (Intl) 提供支援。

20.7 文字的原子:碼點、JavaScript 字元、字位群集

快速回顧 §19「Unicode – 簡要介紹」

下列程式碼示範單一碼點包含一個或兩個 JavaScript 字元。我們透過 .length 來計算後者

// 3 code points, 3 JavaScript characters:
assert.equal('abc'.length, 3);

// 1 code point, 2 JavaScript characters:
assert.equal('🙂'.length, 2);

下列表格總結我們剛剛探討的概念

實體 大小 透過編碼
JavaScript 字元(UTF-16 碼元) 16 位元
Unicode 碼點 21 位元 1–2 個碼元
Unicode 字形叢集 1+ 個碼點

20.7.1 使用碼點

讓我們探討 JavaScript 用於處理碼點的工具。

Unicode 碼點轉譯 讓我們可以十六進位(1–5 個數字)指定碼點。它會產生一個或兩個 JavaScript 字元。

> '\u{1F642}'
'🙂'

  Unicode 轉譯序列

在 ECMAScript 語言規範中,Unicode 碼點轉譯Unicode 碼元轉譯(我們稍後會遇到)稱為 Unicode 轉譯序列

String.fromCodePoint() 將單一碼點轉換為 1–2 個 JavaScript 字元

> String.fromCodePoint(0x1F642)
'🙂'

.codePointAt() 將 1–2 個 JavaScript 字元轉換為單一碼點

> '🙂'.codePointAt(0).toString(16)
'1f642'

我們可以迭代字串,它會拜訪碼點(不是 JavaScript 字元)。迭代在 本書稍後 有說明。一種迭代方式是透過 for-of 迴圈

const str = '🙂a';
assert.equal(str.length, 3);

for (const codePointChar of str) {
  console.log(codePointChar);
}

// Output:
// '🙂'
// 'a'

Array.from() 也基於迭代,而且會拜訪碼點

> Array.from('🙂a')
[ '🙂', 'a' ]

這讓它成為計算碼點的良好工具

> Array.from('🙂a').length
2
> '🙂a'.length
3

20.7.2 使用碼元(字元碼)

指標和字串長度是基於 JavaScript 字元(由 UTF-16 碼元表示)。

若要十六進位指定碼元,我們可以使用包含四個十六進位數位的Unicode 碼元跳脫字元

> '\uD83D\uDE42'
'🙂'

我們可以使用 String.fromCharCode()字元碼是標準函式庫對碼元的名稱

> String.fromCharCode(0xD83D) + String.fromCharCode(0xDE42)
'🙂'

若要取得字元的字元碼,請使用 .charCodeAt()

> '🙂'.charCodeAt(0).toString(16)
'd83d'

20.7.3 ASCII 跳脫字元

如果字元的碼點低於 256,我們可以使用包含兩個十六進位數位的ASCII 跳脫字元來參照它

> 'He\x6C\x6Co'
'Hello'

(ASCII 跳脫字元的正式名稱為十六進位跳脫字元序列 – 它是第一個使用十六進位數字的跳脫字元。)

20.7.4 注意事項:字形叢集

在處理可能以任何人類語言撰寫的文字時,最好在字形叢集的邊界處進行分割,而不是在碼點的邊界處進行分割。

TC39 正在處理 Intl.Segmenter,這是 ECMAScript 國際化 API 的提案,用於支援 Unicode 分割(沿著字形叢集邊界、字詞邊界、句子邊界等)。

在該提案成為標準之前,我們可以使用幾個可用的函式庫之一(在網路上搜尋「JavaScript 字形叢集」)。

20.8 快速參考:字串

20.8.1 轉換為字串

表 14 說明如何將各種值轉換為字串。

表格 14:將值轉換為字串。
x String(x)
未定義 '未定義'
null 'null'
布林值 false 'false'true 'true'
數字 範例:123 '123'
大整數 範例:123n '123'
字串 x(輸入,不變更)
符號 範例:Symbol('abc') 'Symbol(abc)'
物件 可透過 toString() 等方式設定

20.8.2 文字原子的數字值

20.8.3 String.prototype:尋找和比對

(String.prototype 是儲存字串方法的地方。)

20.8.4 String.prototype:萃取

20.8.5 String.prototype:組合

20.8.6 String.prototype:轉換

20.8.7 來源

  練習:使用字串方法

exercises/strings/remove_extension_test.mjs

  測驗

請參閱 測驗應用程式