## 18.字符串
> 原文: [http://exploringjs.com/impatient-js/ch_strings.html](http://exploringjs.com/impatient-js/ch_strings.html)
>
> 貢獻者:[飛龍](https://github.com/wizardforcel)
字符串是 JavaScript 中的原始值和不可變值。也就是說,與字符串相關的操作總是會生成新的字符串,并且永遠不會更改。
### 18.1。普通字符串字面值
純字符串字面值由單引號或雙引號分隔:
```js
const str1 = 'abc';
const str2 = "abc";
assert.equal(str1, str2);
```
單引號更常用,因為它更容易編寫 HTML,其中推薦雙引號。
[下一章](ch_template-literals.html)涵蓋*模板字面值*,它們為您提供:
* 字符串插值
* 多行
* 原始字符串字面值(反斜杠沒有特殊含義)
#### 18.1.1。轉義
反斜杠可讓您創建特殊字符:
* Unix 換行符:`'\n'`
* Windows 換行符:`'\r\n'`
* 標簽:`'\t'`
* 反斜杠:`'\\'`
反斜杠還允許您在字面值內使用字符串字面值的分隔符:
```js
'She said: "Let\'s go!"'
"She said: \"Let's go!\""
```
### 18.2。訪問字符和代碼點
#### 18.2.1。訪問 JavaScript 字符
JavaScript 沒有字符的額外數據類型 - 字符總是作為字符串傳輸。
```js
const str = 'abc';
// Reading a character at a given index
assert.equal(str[1], 'b');
// Counting the characters in a string:
assert.equal(str.length, 3);
```
#### 18.2.2。通過`for-of`訪問 Unicode 代碼點以及展開
通過`for-of`或展開(`...`)迭代字符串訪問 Unicode 代碼點。每個代碼點由 1-2 個 JavaScript 字符表示。有關詳細信息,請參閱[文本原子](ch_strings.html#atoms-of-text)部分。
這是通過`for-of`迭代字符串的代碼點的方法:
```js
for (const ch of 'abc') {
console.log(ch);
}
// Output:
// 'a'
// 'b'
// 'c'
```
這就是通過展開將字符串轉換為代碼點數組的方法:
```js
assert.deepEqual([...'abc'], ['a', 'b', 'c']);
```
### 18.3。通過`+`進行字符串連接
如果至少有一個操作數是字符串,則加號運算符(`+`)將任何非字符串轉換為字符串并連接結果:
```js
assert.equal(3 + ' times ' + 4, '3 times 4');
```
如果要逐個組合字符串,則賦值運算符`+=`非常有用:
```js
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.js`
### 18.4。轉換為字符串
這是將值`x`轉換為字符串的三種方法:
* `String(x)`
* `''+x`
* `x.toString()`(對`undefined`和`null`不起作用)
建議:使用描述性和安全的`String()`。
例子:
```js
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()`將其轉換回來。
```js
> String(false)
'false'
> Boolean('false')
true
```
#### 18.4.1。字符串化對象
普通對象具有一個不太有用的默認表示:
```js
> String({a: 1})
'[object Object]'
```
數組有更好的字符串表示,但它仍然隱藏了很多信息:
```js
> 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'
```
字符串化函數返回其源代碼:
```js
> String(function f() {return 4})
'function f() {return 4}'
```
#### 18.4.2。自定義對象的字符串化
您可以通過實現方法`toString()`來覆蓋字符串化對象的內置方式:
```js
const obj = {
toString() {
return 'hello';
}
};
assert.equal(String(obj), 'hello');
```
#### 18.4.3。字符串化值的另一種方法
JSON 數據格式是 JavaScript 值的文本表示。因此,`JSON.stringify()`也可用于字符串化數據:
```js
> JSON.stringify({a: 1})
'{"a":1}'
> JSON.stringify(['a', ['b']])
'["a",["b"]]'
```
需要注意的是,JSON 僅支持`null`,布爾值,數字,字符串,數組和對象(它總是將它們視為由對象字面值創建)。
提示:第三個參數允許您打開多行輸出并指定縮進量。例如:
```js
console.log(JSON.stringify({first: 'Jane', last: 'Doe'}, null, 2));
```
該語句產生以下輸出。
```json
{
"first": "Jane",
"last": "Doe"
}
```
### 18.5。比較字符串
可以通過以下運算符比較字符串:
```js
< <= > >=
```
需要考慮一個重要的警告:這些運算符基于 JavaScript 字符的數值進行比較。這意味著 JavaScript 用于字符串的順序與字典和電話簿中使用的順序不同:
```js
> 'A' < 'B' // ok
true
> 'a' < 'B' // not ok
false
> '?' < 'b' // not ok
false
```
正確比較文本超出了本書的范圍。它通過[ ECMAScript 國際化 API(`Intl`)](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Intl)得到支持。
### 18.6。文本原子:JavaScript 字符,代碼點,字形簇
快速回顧[關于 Unicode](ch_unicode.html) 的章節:
* 代碼點:Unicode 字符,范圍為?? 21 位。
* UTF-16 代碼單元:JavaScript 字符,范圍為 ??16 位。代碼點編碼為 1-2 個 UTF-16 代碼單元。
* 字形簇:*字素*是書面符號,顯示在屏幕或紙上。*字形簇*是編碼字素的 1 個或多個代碼點的序列。
要在 JavaScript 字符串中表示代碼點,請使用一個或兩個 JavaScript 字符。通過`.length`計算字符時可以看到:
```js
// 3 Unicode code points, 3 JavaScript characters:
assert.equal('abc'.length, 3);
// 1 Unicode code point, 2 JavaScript characters:
assert.equal('??'.length, 2);
```
#### 18.6.1。使用代碼點
讓我們探索 JavaScript 用于處理代碼點的工具。
*代碼點轉義*允許您以十六進制的方式指定代碼點。它們擴展為一個或兩個 JavaScript 字符。
```js
> '\u{1F642}'
'??'
```
從代碼點轉換:
```js
> String.fromCodePoint(0x1F642)
'??'
```
轉換為代碼點:
```js
> '??'.codePointAt(0).toString(16)
'1f642'
```
迭代代碼點。例如,基于迭代的`for-of`循環:
```js
const str = '??a';
assert.equal(str.length, 3);
for (const codePoint of str) {
console.log(codePoint);
}
// Output:
// '??'
// 'a'
```
或基于迭代的展開(`...`):
```js
> [...'??a']
[ '??', 'a' ]
```
因此,展開是計算代碼點的好工具:
```js
> [...'??a'].length
2
> '??a'.length
3
```
#### 18.6.2。使用代碼單元
字符串的索引和長度基于 JavaScript 字符(即代碼單元)。
要以數字方式指定代碼單元,可以使用*代碼單元轉義*:
```js
> '\uD83D\uDE42'
'??'
```
你可以使用所謂的*字符代碼*:
```js
> String.fromCharCode(0xD83D) + String.fromCharCode(0xDE42)
'??'
```
要獲取字符的字符代碼,請使用`.charCodeAt()`:
```js
> '??'.charCodeAt(0).toString(16)
'd83d'
```
#### 18.6.3。警告:字形簇
處理可能使用任何人類語言編寫的文本時,最好在字形簇的邊界處進行拆分,而不是在代碼單元的邊界處進行拆分。
TC39 正在開發[`Intl.Segmenter`](https://github.com/tc39/proposal-intl-segmenter),這是一項支持 ECMAScript 國際化 API 的提議,支持 Unicode 分段(沿著字形簇邊界,字邊界,句子邊界等)。
在該提案成為標準之前,您可以使用幾個可用庫中的一個(對“JavaScript 字形”進行 Web 搜索)。
### 18.7。快速參考:字符串
字符串是不可變的,沒有任何字符串方法可以修改它們。
#### 18.7.1。轉換為字符串
TBL。 [16](#tbl:converting-to-string) 描述了各種值如何轉換為字符串。
Table 16: Converting values to strings.
| `x` | `String(x)` |
| --- | --- |
| `undefined` | `'undefined'` |
| `null` | `'null'` |
| 布爾值 | `false → 'false'`,`true → 'true'` |
| 數值 | 示例:`123 → '123'` |
| 字符串值 | `x`(輸入,未更改) |
| 對象 | 可配置,例如通過`toString()` |
#### 18.7.2。字符和代碼點的數字值
* **字符代碼:** 使用數值表示 JavaScript 字符,Unicode 代碼單元的 JavaScript 名稱
* `String.fromCharCode()`,`String.prototype.charCodeAt()`
* 精度:16 位,無符號
* **代碼點:** 使用數值表示 Unicode 字符
* `String.fromCodePoint()`,`String.prototype.codePointAt()`
* 精度:21 位,無符號(17 個平面,每個 16 位)
#### 18.7.3。字符串運算符
```js
// Access characters via []
const str = 'abc';
assert.equal(str[1], 'b');
// Concatenate strings via +
assert.equal('a' + 'b' + 'c', 'abc');
assert.equal('take ' + 3 + ' oranges', 'take 3 oranges');
```
#### 18.7.4。 `String.prototype`:尋找和匹配
* `.endsWith(searchString: string, endPos=this.length): boolean` <sup>[ES6]</sup>
如果字符串的長度為`endPos`的子串以`searchString`結束,則返回`true`。否則返回`false`。
```js
> 'foo.txt'.endsWith('.txt')
true
> 'abcde'.endsWith('cd', 4)
true
```
* `.includes(searchString: string, startPos=0): boolean` <sup>[ES6]</sup>
如果字符串包含`searchString`,則返回`true`,否則為`false`。搜索從`startPos`開始。
```js
> 'abc'.includes('b')
true
> 'abc'.includes('b', 2)
false
```
* `.indexOf(searchString: string, minIndex=0): number` <sup>[ES1]</sup>
返回字符串中`searchString`出現的最低索引,否則返回`-1`。任何返回的索引都是`minIndex`或更高。
```js
> 'abab'.indexOf('a')
0
> 'abab'.indexOf('a', 1)
2
> 'abab'.indexOf('c')
-1
```
* `.lastIndexOf(searchString: string, maxIndex=Infinity): number` <sup>[ES1]</sup>
返回字符串中出現`searchString`的最高索引,否則返回`-1`。任何返回的索引都是`maxIndex`或更低。
```js
> 'abab'.lastIndexOf('ab', 2)
2
> 'abab'.lastIndexOf('ab', 1)
0
> 'abab'.lastIndexOf('ab')
2
```
* `.match(regExp: string | RegExp): RegExpMatchArray | null` <sup>[ES3]</sup>
如果`regExp`是未設置標志`/g`的正則表達式,則`.match()`返回字符串中`regExp`的第一個匹配項。或者如果沒有匹配項則為`null`。如果`regExp`是字符串,則在執行前面的步驟之前,它用于創建正則表達式。
結果具有以下類型:
```js
interface RegExpMatchArray extends Array<string> {
index: number;
input: string;
groups: undefined | {
[key: string]: string
};
}
```
編號捕獲組成為數組索引。 [命名捕獲組](http://exploringjs.com/es2018-es2019/ch_regexp-named-capture-groups.html)(ES2018)成為`.groups`的屬性。在此模式下,`.match()`的作用類似于`RegExp.prototype.exec()`。
例子:
```js
> '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
```
* `.match(regExp: RegExp): string[] | null` <sup>[ES3]</sup>
如果設置了`regExp`的標志`/g`,`.match()`將返回一個包含所有匹配項的數組,如果沒有匹配項則返回`null`。
```js
> '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` <sup>[ES3]</sup>
返回字符串中`regExp`出現的索引。如果`regExp`是字符串,則用于創建正則表達式。
```js
> 'a2b'.search(/[0-9]/)
1
> 'a2b'.search('[0-9]')
1
```
* `.startsWith(searchString: string, startPos=0): boolean` <sup>[ES6]</sup>
如果`searchString`出現字符串中索引`startPos`處,則返回`true`。否則返回`false`。
```js
> '.gitignore'.startsWith('.')
true
> 'abcde'.startsWith('bc', 1)
true
```
#### 18.7.5。 `String.prototype`:提取
* `.slice(start=0, end=this.length): string` <sup>[ES3]</sup>
返回從索引`start`開始(包括)并以索引`end`結束(不包括)的字符串的子串。您可以使用負指數,其中`-1`表示`this.length-1`(以此類推)。
```js
> 'abc'.slice(1, 3)
'bc'
> 'abc'.slice(1)
'bc'
> 'abc'.slice(-2)
'bc'
```
* `.split(separator: string | RegExp, limit?: number): string[]` <sup>[ES3]</sup>
將字符串拆分為子字符串數組 - 分隔符之間出現的字符串。分隔符可以是字符串或正則表達式。正則表達式中的捕獲組包含在結果中。
```js
> 'abc'.split('')
[ 'a', 'b', 'c' ]
> 'a | b | c'.split('|')
[ 'a ', ' b ', ' c' ]
> 'a : b : c'.split(/ *: */)
[ 'a', 'b', 'c' ]
> 'a : b : c'.split(/( *):( *)/)
[ 'a', ' ', ' ', 'b', ' ', ' ', 'c' ]
```
* `.substring(start: number, end=this.length): string` <sup>[ES1]</sup>
使用`.slice()`代替此方法。 `.substring()`未在舊引擎中一致地實現,并且不支持負指數。
#### 18.7.6。 `String.prototype`:合并
* `.concat(...strings: string[]): string` <sup>[ES3]</sup>
返回字符串和`strings`的連接。 `'a'+'b'`相當于`'a'.concat('b')`,更簡潔。
```js
> 'ab'.concat('cd', 'ef', 'gh')
'abcdefgh'
```
* `.padEnd(len: number, fillString=' '): string` <sup>[ES2017]</sup>
將`fillString`追加到字符串,直到它具有所需的長度`len`。
```js
> '#'.padEnd(2)
'# '
> 'abc'.padEnd(2)
'abc'
> '#'.padEnd(5, 'abc')
'#abca'
```
* `.padStart(len: number, fillString=' '): string` <sup>[ES2017]</sup>
將`fillString`添加到字符串頭部,直到它具有所需的長度`len`。
```js
> '#'.padStart(2)
' #'
> 'abc'.padStart(2)
'abc'
> '#'.padStart(5, 'abc')
'abca#'
```
* `.repeat(count=0): string` <sup>[ES6]</sup>
返回一個字符串,重復`count`次。
```js
> '*'.repeat()
''
> '*'.repeat(3)
'***'
```
#### 18.7.7。 `String.prototype`:轉換
* `.normalize(form: 'NFC'|'NFD'|'NFKC'|'NFKD' = 'NFC'): string` <sup>[ES6]</sup>
根據 [Unicode 規范化形式](https://unicode.org/reports/tr15/)規范化字符串。
* `.replace(searchValue: string | RegExp, replaceValue: string): string` <sup>[ES3]</sup>
將`searchValue`的匹配項替換為`replaceValue`。如果`searchValue`是一個字符串,則只替換第一個出現位置。如果`searchValue`是沒有標志`/g`的正則表達式,則僅替換第一個匹配項。如果`searchValue`是帶有`/g`的正則表達式,則替換所有匹配項。
```js
> 'x.x.'.replace('.', '#')
'x#x.'
> 'x.x.'.replace(/./, '#')
'#.x.'
> 'x.x.'.replace(/./g, '#')
'####'
```
`replaceValue`中的特殊字符是:
* `$$`:成為`$`
* `$n`:成為編號捕獲組`n`(唉,`$0`不起作用)
* `$&`:成為完全匹配項
* `$``:成為匹配項前的任何東西
* `$'`:成為匹配項后的任何東西
例子:
```js
> 'a 2020-04 b'.replace(/([0-9]{4})-([0-9]{2})/, '|$2|')
'a |04| b'
> 'a 2020-04 b'.replace(/([0-9]{4})-([0-9]{2})/, '|$&|')
'a |2020-04| b'
> 'a 2020-04 b'.replace(/([0-9]{4})-([0-9]{2})/, '|$`|')
'a |a | b'
```
[也支持命名捕獲組](http://exploringjs.com/es2018-es2019/ch_regexp-named-capture-groups.html)(ES2018):
* `$<name>`成為命名捕獲組`name
例:
```js
> 'a 2020-04 b'.replace(/(?<year>[0-9]{4})-(?<month>[0-9]{2})/, '|$<month>|')
'a |04| b'
```
* `.replace(searchValue: string | RegExp, replacer: (...args: any[]) => string): string` <sup>[ES3]</sup>
如果第二個參數是函數,則替換為其返回的字符串。其參數`args`是:
* `matched: string`:完全匹配項
* `g1: string|undefined`:編號捕獲組 1
* `g2: string|undefined`:編號捕獲組 2
* (等等)
* `offset: number`:在輸入字符串中找到匹配項的位置?
* `input: string`:整個輸入字符串
```js
const regexp = /([0-9]{4})-([0-9]{2})/;
const replacer = (all, year, month) => '|' + all + '|';
assert.equal(
'a 2020-04 b'.replace(regexp, replacer),
'a |2020-04| b');
```
[也支持命名捕獲組](http://exploringjs.com/es2018-es2019/ch_regexp-named-capture-groups.html)(ES2018)。如果有,則最后一個參數包含一個對象,其屬性包含捕獲組:
```js
const regexp = /(?<year>[0-9]{4})-(?<month>[0-9]{2})/;
const replacer = (...args) => {
const groups=args.pop();
return '|' + groups.month + '|';
};
assert.equal(
'a 2020-04 b'.replace(regexp, replacer),
'a |04| b');
```
* `.toUpperCase(): string` <sup>[ES1]</sup>
返回字符串的副本,其中所有小寫字母字符都轉換為大寫。對于各種字母表的效果取決于 JavaScript 引擎。
```js
> '-a2b-'.toUpperCase()
'-A2B-'
> 'αβγ'.toUpperCase()
'ΑΒΓ
```
* `.toLowerCase(): string` <sup>[ES1]</sup>
返回字符串的副本,其中所有大寫字母字符都轉換為小寫。對于各種字母表的效果取決于 JavaScript 引擎。
```js
> '-A2B-'.toLowerCase()
'-a2b-'
> 'ΑΒΓ'.toLowerCase()
'αβγ'
```
* `.trim(): string` <sup>[ES5]</sup>
返回字符串的副本,其中所有前導和尾隨空白(空格,制表符,行終止符等)都去掉了。
```js
> '\r\n#\t '.trim()
'#'
```
* `.trimEnd(): string` <sup>[ES2019]</sup>
與`.trim()`類似,但僅修剪字符串的末尾:
```js
> ' abc '.trimEnd()
' abc'
```
* `.trimStart(): string` <sup>[ES2019]</sup>
與`.trim()`類似,但僅修剪字符串的開頭:
```js
> ' abc '.trimStart()
'abc '
```
#### 18.7.8。 `String.prototype`:字符,字符代碼,代碼點
* `.charAt(pos: number): string` <sup>[ES1]</sup>
返回索引`pos`處的字符,作為字符串(JavaScript 沒有字符的數據類型)。 `str[i]`等同于`str.charAt(i)`并且更簡潔(警告:可能不適用于舊引擎)。
```js
> 'abc'.charAt(1)
'b'
```
* `.charCodeAt(pos: number): number` <sup>[ES1]</sup>
返回索引`pos`處的 UTF-16 代碼單元(字符)的 16 位數字(0-65535)。
```js
> 'abc'.charCodeAt(1)
98
```
* `.codePointAt(pos: number): number | undefined` <sup>[ES6]</sup>
返回索引`pos`處 1-2 個字符的 Unicode 代碼點的 21 位數。如果沒有這樣的索引,則返回`undefined`。
#### 18.7.9。來源
* [TypeScript 的內置類型](https://github.com/Microsoft/TypeScript/blob/master/lib/)
* [JavaScript 的 MDN 網絡文檔](https://developer.mozilla.org/en-US/docs/Web/JavaScript)
* [ECMAScript 語言規范](https://tc39.github.io/ecma262/)
 **練習:使用字符串方法**
`exercises/strings/remove_extension_test.js`
 **測驗**
參見[測驗應用程序](ch_quizzes-exercises.html#quizzes)。
- I.背景
- 1.關于本書(ES2019 版)
- 2.常見問題:本書
- 3. JavaScript 的歷史和演變
- 4.常見問題:JavaScript
- II.第一步
- 5.概覽
- 6.語法
- 7.在控制臺上打印信息(console.*)
- 8.斷言 API
- 9.測驗和練習入門
- III.變量和值
- 10.變量和賦值
- 11.值
- 12.運算符
- IV.原始值
- 13.非值undefined和null
- 14.布爾值
- 15.數字
- 16. Math
- 17. Unicode - 簡要介紹(高級)
- 18.字符串
- 19.使用模板字面值和標記模板
- 20.符號
- V.控制流和數據流
- 21.控制流語句
- 22.異常處理
- 23.可調用值
- VI.模塊化
- 24.模塊
- 25.單個對象
- 26.原型鏈和類
- 七.集合
- 27.同步迭代
- 28.數組(Array)
- 29.類型化數組:處理二進制數據(高級)
- 30.映射(Map)
- 31. WeakMaps(WeakMap)
- 32.集(Set)
- 33. WeakSets(WeakSet)
- 34.解構
- 35.同步生成器(高級)
- 八.異步
- 36. JavaScript 中的異步編程
- 37.異步編程的 Promise
- 38.異步函數
- IX.更多標準庫
- 39.正則表達式(RegExp)
- 40.日期(Date)
- 41.創建和解析 JSON(JSON)
- 42.其余章節在哪里?