写给没耐心程序员的 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,而 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 grapheme”)。

20.8 快速参考:字符串

20.8.1 转换为字符串

表 14 描述了如何将各种值转换为字符串。

表 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() 进行配置

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

  测验

请参阅 测验应用程序