+
進行字串串接String.prototype
:尋找和配對String.prototype
:擷取String.prototype
:組合String.prototype
:轉換字串是 JavaScript 中的基本值,且不可變。也就是說,與字串相關的操作總是會產生新的字串,而不會變更現有的字串。
字串的字面值
const str1 = 'Don\'t say "goodbye"'; // string literal
const str2 = "Don't say \"goodbye\""; // string literals
.equal(
assert`As easy as ${123}!`, // template literal
'As easy as 123!',
; )
反斜線用於
\\
表示反斜線\n
表示換行\r
表示回車\t
表示 tab在 String.raw
標記模板(第 A 行)中,反斜線被視為一般字元
.equal(
assertString.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';
.equal(
assert2], 'c' // no negative indices allowed
str3[;
).equal(
assert.at(-1), 'c' // negative indices allowed
str3;
)
// Copying more than one character:
.equal(
assert'abc'.slice(0, 2), 'ab'
; )
串接字串
.equal(
assert'I bought ' + 3 + ' apples',
'I bought 3 apples',
;
)
let str = '';
+= 'I bought ';
str += 3;
str += ' apples';
str .equal(
assert, 'I bought 3 apples',
str; )
JavaScript 字元大小為 16 位元。它們是字串中編入索引的內容,也是 .length
計算的內容。
代碼點是 Unicode 文字的原子部分。它們大多符合一個 JavaScript 字元,有些則佔用兩個(特別是表情符號)
.equal(
assert'A'.length, 1
;
).equal(
assert'🙂'.length, 2
; )
字形群集(使用者感知的字元)代表書寫符號。每個字形群集包含一個或多個代碼點。
由於這些事實,我們不應該將文字分割成 JavaScript 字元,我們應該將它分割成字形群集。有關如何處理文字的更多資訊,請參閱 §20.7「文字的原子:代碼點、JavaScript 字元、字形群集」。
此小節簡要概述字串 API。本章節的結尾有一個 更全面的快速參考。
尋找子字串
> 'abca'.includes('a')true
> 'abca'.startsWith('ab')true
> 'abca'.endsWith('ca')true
> 'abca'.indexOf('a')0
> 'abca'.lastIndexOf('a')3
分割和合併
.deepEqual(
assert'a, b,c'.split(/, ?/),
'a', 'b', 'c']
[;
).equal(
assert'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()'αβγ'
一般字串文字以單引號或雙引號為界
const str1 = 'abc';
const str2 = "abc";
.equal(str1, str2); assert
單引號使用較為頻繁,因為這樣可以更容易地提及 HTML,其中較常使用雙引號。
下一章涵蓋模板文字,它提供我們
反斜線讓我們可以建立特殊字元
'\n'
'\r\n'
'\t'
'\\'
反斜線也讓我們可以在字串文字中使用字串文字的分隔符號
.equal(
assert'She said: "Let\'s go!"',
"She said: \"Let's go!\"");
JavaScript 沒有額外的資料類型來表示字元,字元總是表示為字串。
const str = 'abc';
// Reading a JavaScript character at a given index
.equal(str[1], 'b');
assert
// Counting the JavaScript characters in a string:
.equal(str.length, 3); assert
我們在螢幕上看到的字元稱為字形群集。它們大多數由單一 JavaScript 字元表示。但是,也有由多個 JavaScript 字元表示的字形群集(特別是表情符號)
> '🙂'.length2
其運作方式說明於 §20.7 「文字的原子:碼點、JavaScript 字元、字位群集」。
+
進行字串串接如果至少一個運算元是字串,加號運算子 (+
) 會將任何非字串轉換為字串,並串接結果
.equal(3 + ' times ' + 4, '3 times 4'); assert
如果我們想要逐一組裝字串,賦值運算子 +=
會很有用
let str = ''; // must be `let`!
+= 'Say it';
str += ' one more';
str += ' time';
str
.equal(str, 'Say it one more time'); assert
透過
+
進行串接很有效率
使用 +
組裝字串相當有效率,因為大多數 JavaScript 引擎會在內部最佳化它。
練習:串接字串
exercises/strings/concat_string_array_test.mjs
以下有轉換值 x
為字串的三種方式
String(x)
''+x
x.toString()
(對 undefined
和 null
無效)建議:使用具描述性且安全的 String()
。
範例
.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'); assert
布林值的陷阱:如果我們透過 String()
將布林值轉換為字串,通常無法透過 Boolean()
將其轉換回來
> String(false)'false'
> Boolean('false')true
Boolean()
會傳回 false
的唯一字串是空字串。
純粹的物件有一個預設的字串表示法,但不太實用
> 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}'
我們可以透過實作 toString()
方法來覆寫內建的物件字串化方式
const obj = {
toString() {
return 'hello';
};
}
.equal(String(obj), 'hello'); assert
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"
}
字串可以使用以下運算子進行比較
< <= > >=
有一個重要的注意事項:這些運算子會根據 JavaScript 字元的數字值進行比較。這表示 JavaScript 使用的字串順序與字典和電話簿中使用的順序不同
> 'A' < 'B' // oktrue
> 'a' < 'B' // not okfalse
> 'ä' < 'b' // not okfalse
正確比較文字超出了本書的範圍。它透過 ECMAScript 國際化 API (Intl
) 提供支援。
快速回顧 §19「Unicode – 簡要介紹」
碼點 是 Unicode 文字的原子部分。每個碼點的大小為 21 位元。
JavaScript 字串透過編碼格式 UTF-16 來實作 Unicode。它使用一個或兩個 16 位元的 碼元 來編碼單一碼點。
字形叢集(使用者感知的字元)代表書寫符號,就像顯示在螢幕或紙張上一樣。需要一個或多個碼點才能編碼單一字形叢集。
下列程式碼示範單一碼點包含一個或兩個 JavaScript 字元。我們透過 .length
來計算後者
// 3 code points, 3 JavaScript characters:
.equal('abc'.length, 3);
assert
// 1 code point, 2 JavaScript characters:
.equal('🙂'.length, 2); assert
下列表格總結我們剛剛探討的概念
實體 | 大小 | 透過編碼 |
---|---|---|
JavaScript 字元(UTF-16 碼元) | 16 位元 | – |
Unicode 碼點 | 21 位元 | 1–2 個碼元 |
Unicode 字形叢集 | 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';
.equal(str.length, 3);
assert
for (const codePointChar of str) {
console.log(codePointChar);
}
// Output:
// '🙂'
// 'a'
Array.from()
也基於迭代,而且會拜訪碼點
> Array.from('🙂a')[ '🙂', 'a' ]
這讓它成為計算碼點的良好工具
> Array.from('🙂a').length2
> '🙂a'.length3
指標和字串長度是基於 JavaScript 字元(由 UTF-16 碼元表示)。
若要十六進位指定碼元,我們可以使用包含四個十六進位數位的Unicode 碼元跳脫字元
> '\uD83D\uDE42''🙂'
我們可以使用 String.fromCharCode()
。字元碼是標準函式庫對碼元的名稱
> String.fromCharCode(0xD83D) + String.fromCharCode(0xDE42)'🙂'
若要取得字元的字元碼,請使用 .charCodeAt()
> '🙂'.charCodeAt(0).toString(16)'d83d'
如果字元的碼點低於 256,我們可以使用包含兩個十六進位數位的ASCII 跳脫字元來參照它
> 'He\x6C\x6Co''Hello'
(ASCII 跳脫字元的正式名稱為十六進位跳脫字元序列 – 它是第一個使用十六進位數字的跳脫字元。)
在處理可能以任何人類語言撰寫的文字時,最好在字形叢集的邊界處進行分割,而不是在碼點的邊界處進行分割。
TC39 正在處理 Intl.Segmenter
,這是 ECMAScript 國際化 API 的提案,用於支援 Unicode 分割(沿著字形叢集邊界、字詞邊界、句子邊界等)。
在該提案成為標準之前,我們可以使用幾個可用的函式庫之一(在網路上搜尋「JavaScript 字形叢集」)。
表 14 說明如何將各種值轉換為字串。
x |
String(x) |
---|---|
未定義 |
'未定義' |
null |
'null' |
布林值 | false → 'false' ,true → 'true' |
數字 | 範例:123 → '123' |
大整數 | 範例:123n → '123' |
字串 | x (輸入,不變更) |
符號 | 範例:Symbol('abc') → 'Symbol(abc)' |
物件 | 可透過 toString() 等方式設定 |
String.fromCharCode()
[ES1].charCodeAt()
[ES1]String.fromCodePoint()
[ES6].codePointAt()
[ES6]String.prototype
:尋找和比對(String.prototype
是儲存字串方法的地方。)
.endsWith(searchString: string, endPos=this.length): boolean
[ES6]
如果字串的長度為 endPos
,則字串以 searchString
結尾時傳回 true
。否則傳回 false
。
> 'foo.txt'.endsWith('.txt')true
> 'abcde'.endsWith('cd', 4)true
.includes(searchString: string, startPos=0): boolean
[ES6]
如果字串包含 searchString
,則傳回 true
,否則傳回 false
。搜尋從 startPos
開始。
> 'abc'.includes('b')true
> 'abc'.includes('b', 2)false
.indexOf(searchString: string, minIndex=0): number
[ES1]
傳回 searchString
在字串中出現的最低索引,否則傳回 -1
。任何傳回的索引都會是 minIndex
或更高。
> 'abab'.indexOf('a')0
> 'abab'.indexOf('a', 1)2
> 'abab'.indexOf('c')-1
.lastIndexOf(searchString: string, maxIndex=Infinity): number
[ES1]
傳回 searchString
在字串中出現的最高索引,否則傳回 -1
。任何傳回的索引都會是 maxIndex
或更低。
> 'abab'.lastIndexOf('ab', 2)2
> 'abab'.lastIndexOf('ab', 1)0
> 'abab'.lastIndexOf('ab')2
[1 of 2] .match(regExp: string | RegExp): RegExpMatchArray | null
[ES3]
如果 regExp
是未設定旗標 /g
的正規表示式,則 .match()
會傳回 regExp
在字串中的第一個比對。如果沒有比對,則傳回 null
。如果 regExp
是字串,則會用來建立正規表示式(想成 new RegExp()
的參數),然後執行前面提到的步驟。
結果具有下列型別
interface RegExpMatchArray extends Array<string> {
: number;
index: string;
input: undefined | {
groups: string]: string
[key;
} }
編號的擷取群組會變成陣列索引(這就是為什麼這個型別會延伸 Array
)。命名擷取群組(ES2018)會變成 .groups
的屬性。在此模式中,.match()
的運作方式類似 RegExp.prototype.exec()
。
範例
> 'ababb'.match(/a(b+)/){ 0: 'ab', 1: 'b', index: 0, input: 'ababb', groups: undefined }
> 'ababb'.match(/a(?<foo>b+)/){ 0: 'ab', 1: 'b', index: 0, input: 'ababb', groups: { foo: 'b' } }
> 'abab'.match(/x/)null
[2 of 2] .match(regExp: RegExp): string[] | null
[ES3]
如果 regExp
的旗標 /g
已設定,則 .match()
會傳回包含所有比對的陣列,或者如果沒有比對,則傳回 null
。
> 'ababb'.match(/a(b+)/g)[ 'ab', 'abb' ]
> 'ababb'.match(/a(?<foo>b+)/g)[ 'ab', 'abb' ]
> 'abab'.match(/x/g)null
.search(regExp: string | RegExp): number
[ES3]
傳回 regExp
在字串中出現的索引。如果 regExp
是字串,則會用來建立正規表示式(想成 new RegExp()
的參數)。
> 'a2b'.search(/[0-9]/)1
> 'a2b'.search('[0-9]')1
.startsWith(searchString: string, startPos=0): boolean
[ES6]
如果 searchString
在字串的索引 startPos
中出現,則傳回 true
。否則傳回 false
。
> '.gitignore'.startsWith('.')true
> 'abcde'.startsWith('bc', 1)true
String.prototype
:萃取.slice(start=0, end=this.length): string
[ES3]
傳回從索引 start
(包含)開始,到索引 end
(不包含)結束的字串子字串。如果索引為負值,則會在使用前將其加到 .length
(-1
變成 this.length-1
,以此類推)。
> 'abc'.slice(1, 3)'bc'
> 'abc'.slice(1)'bc'
> 'abc'.slice(-2)'bc'
.at(index: number): string | undefined
[ES2022]
傳回 index
中的 JavaScript 字元作為字串。如果 index
為負值,則會在使用前將其加到 .length
(-1
變成 this.length-1
,以此類推)。
> 'abc'.at(0)'a'
> 'abc'.at(-1)'c'
.split(separator: string | RegExp, limit?: number): string[]
[ES3]
將字串分割成子字串陣列,也就是分隔符號之間的字串。分隔符號可以是字串
> 'a | b | c'.split('|')[ 'a ', ' b ', ' c' ]
也可以是正規表示法
> 'a : b : c'.split(/ *: */)[ 'a', 'b', 'c' ]
> 'a : b : c'.split(/( *):( *)/)[ 'a', ' ', ' ', 'b', ' ', ' ', 'c' ]
最後的呼叫示範了正規表示法中群組所做的擷取會成為傳回陣列的元素。
警告:.split('')
會將字串分割成 JavaScript 字元。在處理星體碼點(編碼為兩個 JavaScript 字元)時,這無法發揮作用。例如,表情符號就是星體
> '🙂X🙂'.split('')[ '\uD83D', '\uDE42', 'X', '\uD83D', '\uDE42' ]
相反地,最好使用 Array.from()
(或展開)
> Array.from('🙂X🙂')[ '🙂', 'X', '🙂' ]
.substring(start: number, end=this.length): string
[ES1]
使用 .slice()
取代此方法。.substring()
在較舊的引擎中實作不一致,且不支援負面索引。
String.prototype
:組合.concat(...strings: string[]): string
[ES3]
傳回字串和 strings
的串接。'a'.concat('b')
等於 'a'+'b'
。後者更受歡迎。
> 'ab'.concat('cd', 'ef', 'gh')'abcdefgh'
.padEnd(len: number, fillString=' '): string
[ES2017]
將 fillString
(片段)附加到字串,直到它達到所需的長度 len
。如果它已經達到或超過 len
,則會在沒有任何變更的情況下傳回。
> '#'.padEnd(2)'# '
> 'abc'.padEnd(2)'abc'
> '#'.padEnd(5, 'abc')'#abca'
.padStart(len: number, fillString=' '): string
[ES2017]
將 fillString
(片段)置於字串之前,直到它達到所需的長度 len
。如果它已經達到或超過 len
,則會在沒有任何變更的情況下傳回。
> '#'.padStart(2)' #'
> 'abc'.padStart(2)'abc'
> '#'.padStart(5, 'abc')'abca#'
.repeat(count=0): string
[ES6]
傳回字串,串接 count
次。
> '*'.repeat()''
> '*'.repeat(3)'***'
String.prototype
:轉換.normalize(form: 'NFC'|'NFD'|'NFKC'|'NFKD' = 'NFC'): string
[ES6]
根據 Unicode 標準化格式 標準化字串。
[1 of 2] .replaceAll(searchValue: string | RegExp, replaceValue: string): string
[ES2021]
如果您無法使用
.replaceAll()
,該怎麼辦
如果目標平台上沒有提供 .replaceAll()
,則可以使用 .replace()
取代。說明請參閱 §43.6.8.1 “str.replace(searchValue, replacementValue)
[ES3]”。
將 searchValue
的所有比對結果替換為 replaceValue
。如果 searchValue
是沒有旗標 /g
的正規表示式,則會擲回 TypeError
。
> 'x.x.'.replaceAll('.', '#')'x#x#'
> 'x.x.'.replaceAll(/./g, '#')'####'
> 'x.x.'.replaceAll(/./, '#')TypeError: String.prototype.replaceAll called with
a non-global RegExp argument
replaceValue
中的特殊字元為
$$
:變為 $
$n
:變為編號群組 n
的擷取結果(可惜的是,$0
代表字串 '$0'
,它並未指涉完整的比對結果)$&
:變為完整的比對結果$`
:變為比對結果之前的所有內容$'
:變為比對結果之後的所有內容範例
> 'a 1995-12 b'.replaceAll(/([0-9]{4})-([0-9]{2})/g, '|$2|')'a |12| b'
> 'a 1995-12 b'.replaceAll(/([0-9]{4})-([0-9]{2})/g, '|$&|')'a |1995-12| b'
> 'a 1995-12 b'.replaceAll(/([0-9]{4})-([0-9]{2})/g, '|$`|')'a |a | b'
命名擷取群組(ES2018)也受支援
$<name>
變為命名群組 name
的擷取結果範例
.equal(
assert'a 1995-12 b'.replaceAll(
/(?<year>[0-9]{4})-(?<month>[0-9]{2})/g, '|$<month>|'),
'a |12| b');
[2/2] .replaceAll(searchValue: string | RegExp, replacer: (...args: any[]) => string): string
[ES2021]
如果第二個參數是函式,則會以函式傳回的字串替換出現的結果。其參數 args
為
matched: string
。完整的比對結果g1: string|undefined
。編號群組 1 的擷取結果g2: string|undefined
。編號群組 2 的擷取結果offset: number
。在輸入字串中找到比對結果的位置input: string
。整個輸入字串const regexp = /([0-9]{4})-([0-9]{2})/g;
const replacer = (all, year, month) => '|' + all + '|';
.equal(
assert'a 1995-12 b'.replaceAll(regexp, replacer),
'a |1995-12| b');
命名擷取群組(ES2018)也受支援。如果有任何命名擷取群組,則會在最後新增一個參數,其物件的屬性包含擷取結果
const regexp = /(?<year>[0-9]{4})-(?<month>[0-9]{2})/g;
const replacer = (...args) => {
const groups=args.pop();
return '|' + groups.month + '|';
;
}.equal(
assert'a 1995-12 b'.replaceAll(regexp, replacer),
'a |12| b');
.replace(searchValue: string | RegExp, replaceValue: string): string
[ES3]
.replace(searchValue: string | RegExp, replacer: (...args: any[]) => string): string
[ES3]
.replace()
的運作方式與 .replaceAll()
相同,但如果 searchValue
是字串或沒有 /g
的正規表示式,則只會替換第一次出現的結果
> 'x.x.'.replace('.', '#')'x#x.'
> 'x.x.'.replace(/./, '#')'#.x.'
有關此方法的詳細資訊,請參閱 §43.6.8.1 “str.replace(searchValue, replacementValue)
[ES3]”。
.toUpperCase(): string
[ES1]
傳回字串的副本,其中所有小寫字母都轉換為大寫。這種運作方式在不同字母表中的效果如何,取決於 JavaScript 引擎。
> '-a2b-'.toUpperCase()'-A2B-'
> 'αβγ'.toUpperCase()'ΑΒΓ'
.toLowerCase(): string
[ES1]
傳回字串的副本,其中所有大寫字母都轉換為小寫。此功能對各種字母系統的運作效果,取決於 JavaScript 引擎。
> '-A2B-'.toLowerCase()'-a2b-'
> 'ΑΒΓ'.toLowerCase()'αβγ'
.trim(): string
[ES5]
傳回字串的副本,其中所有前導和尾隨空白(空白、標籤、換行符號等)都已移除。
> '\r\n#\t '.trim()'#'
> ' abc '.trim()'abc'
.trimEnd(): string
[ES2019]
類似於 .trim()
,但只會修剪字串的結尾
> ' abc '.trimEnd()' abc'
.trimStart(): string
[ES2019]
類似於 .trim()
,但只會修剪字串的開頭
> ' abc '.trimStart()'abc '
練習:使用字串方法
exercises/strings/remove_extension_test.mjs
測驗
請參閱 測驗應用程式。