+
进行字符串连接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
表示制表符在 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,而 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 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> {
: 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 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 + '|';
.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
测验
请参阅 测验应用程序。