+ 进行字符串连接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
assert.equal(
`As easy as ${123}!`, // template literal
'As easy as 123!',
);反斜杠用于
\\ 表示反斜杠\n 表示换行符\r 表示回车符\t 表示制表符在 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',
);JavaScript 字符 的大小为 16 位。它们是在字符串中被索引的内容,也是 .length 计数的内容。
代码点 是 Unicode 文本的原子部分。它们中的大多数都适合一个 JavaScript 字符,而有些则占用两个(尤其是表情符号)
assert.equal(
'A'.length, 1
);
assert.equal(
'🙂'.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拆分和连接
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()
'αβγ'普通字符串字面量由单引号或双引号分隔
const str1 = 'abc';
const str2 = "abc";
assert.equal(str1, str2);单引号的使用频率更高,因为它更容易提及 HTML,而 HTML 中首选双引号。
下一章 将介绍 模板字面量,它为我们提供了
反斜杠让我们可以创建特殊字符
'\n''\r\n''\t''\\'反斜杠还允许我们在字符串字面量中使用该字面量的分隔符
assert.equal(
'She said: "Let\'s go!"',
"She said: \"Let's go!\"");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 字符、字素簇”。
+ 进行字符串连接如果至少有一个操作数是字符串,则加号运算符 (+) 会将任何非字符串转换为字符串,并连接结果
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
以下是将值 x 转换为字符串的三种方法
String(x)''+xx.toString()(不适用于 undefined 和 null)建议:使用描述性且安全的 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')
trueBoolean() 返回 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';
}
};
assert.equal(String(obj), 'hello');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' // ok
true
> 'a' < 'B' // not ok
false
> 'ä' < 'b' // not ok
false正确比较文本超出了本书的范围。它通过 ECMAScript 国际化 API (Intl) 支持。
§19 “Unicode 简介” 的快速回顾
代码点 是 Unicode 文本的原子部分。每个代码点的大小为 21 位。
JavaScript 字符串通过编码格式 UTF-16 实现 Unicode。它使用一个或两个 16 位的 代码单元 来编码单个代码点。
字素簇(用户感知字符)表示书写符号,如在屏幕或纸张上显示的那样。需要一个或多个代码点来编码单个字素簇。
以下代码演示了单个代码点包含一个或两个 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 个或多个代码点 |
让我们探索 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字符串的索引和长度基于 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 grapheme”)。
表 14 描述了如何将各种值转换为字符串。
x |
String(x) |
|---|---|
undefined |
'undefined' |
null |
'null' |
| boolean | false → 'false', true → 'true' |
| number | 示例:123 → '123' |
| bigint | 示例:123n → '123' |
| string | x(输入,不变) |
| symbol | 示例:Symbol('abc') → 'Symbol(abc)' |
| object | 可通过例如 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> {
index: number;
input: string;
groups: undefined | {
[key: string]: string
};
}带编号的捕获组成为数组索引(这就是为什么此类型扩展了 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)
trueString.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 argumentreplaceValue 中的特殊字符是
$$:变为 $$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 的捕获示例
assert.equal(
'a 1995-12 b'.replaceAll(
/(?<year>[0-9]{4})-(?<month>[0-9]{2})/g, '|$<month>|'),
'a |12| b');[2 of 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 + '|';
assert.equal(
'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 + '|';
};
assert.equal(
'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
测验
请参阅 测验应用程序。