第 12 章. 字符串
目录
购买本书
(广告,请不要屏蔽。)

第 12 章. 字符串

字符串是 不可变的 JavaScript 字符序列。每个字符都是一个 16 位 UTF-16 代码单元。这意味着一个 Unicode 字符由一个或两个 JavaScript 字符表示。每当您计算字符数或拆分字符串时,您主要需要注意双字符情况(请参阅第 24 章)。

字符串字面量

单引号和双引号都可以用来 界定字符串字面量:

'He said: "Hello"'
"He said: \"Hello\""

'Everyone\'s a winner'
"Everyone's a winner"

因此,您可以自由使用任何一种引号。但是,有一些注意事项

  • 社区中最常见的风格是使用双引号表示 HTML,使用单引号表示 JavaScript。
  • 另一方面,在某些语言(例如 C 和 Java)中,双引号专门用于字符串。因此,在多语言代码库中使用它们可能是有意义的。
  • 对于 JSON(在 第 22 章 中讨论),您必须使用双引号。

如果您的代码一致地使用引号,它将看起来更干净。但有时,不同的引号意味着您不必转义,这可以证明您不那么一致是合理的(例如,您通常可能使用单引号,但暂时切换到双引号来编写前面最后一个示例)。

字符串字面量中的转义

字符串字面量中的大多数字符 只代表它们自己。反斜杠用于 转义,并启用了几种特殊功能:

行延续

您可以 通过使用反斜杠转义行尾(行终止符,行终止符)将字符串扩展到多行:

var str = 'written \
over \
multiple \
lines';
console.log(str === 'written over multiple lines'); // true

另一种方法是使用 加号运算符进行连接:

var str = 'written ' +
          'over ' +
          'multiple ' +
          'lines';
字符转义序列

这些序列以反斜杠开头:

  • 控制字符:\b 是退格符,\f 是换页符,\n 是换行符,\r 是回车符,\t 是水平制表符,\v 是垂直制表符。
  • 表示自身的转义字符:\' 是单引号,\" 是双引号,\\ 是反斜杠。除 b f n r t v x u 和十进制数字以外的所有字符也代表它们自己。以下是两个例子

    > '\"'
    '"'
    > '\q'
    'q'
NUL 字符(Unicode 代码点 0)
此字符由 \0 表示。
十六进制转义序列

\xHHHH 是两个十六进制数字) 通过 ASCII 代码指定一个字符。例如:

> '\x4D'
'M'
Unicode 转义序列

\uHHHHHHHH 是四个十六进制数字) 指定一个 UTF-16 代码单元(请参阅第 24 章)。以下是两个例子

> '\u004D'
'M'
> '\u03C0'
'π'

字符访问

有两个操作 返回字符串的第 n 个字符。[16] 请注意,JavaScript 没有用于字符的特殊数据类型;这些操作返回字符串

> 'abc'.charAt(1)
'b'
> 'abc'[1]
'b'

一些较旧的浏览器不支持通过方括号进行类似数组的字符访问。

转换为字符串

值按如下方式转换为字符串:

结果

undefined

'undefined'

null

'null'

布尔值

false'false'

true'true'

数字

数字作为字符串(例如,3.141'3.141'

字符串

与输入相同(无需转换)

对象

调用 ToPrimitive(value, String)(请参阅 算法:ToPrimitive()—将值转换为原始值)并转换生成的原始值。

手动转换为字符串

将任何值转换为字符串的三种最常见方法是:

String(value)

(作为函数调用,而不是作为构造函数调用)

''+value

value.toString()

(不适用于 undefinednull!)

我更喜欢 String(),因为它更具描述性。以下是一些例子

> String(false)
'false'
> String(7.35)
'7.35'
> String({ first: 'John', last: 'Doe' })
'[object Object]'
> String([ 'a', 'b', 'c' ])
'a,b,c'

请注意,对于显示数据,JSON.stringify() (JSON.stringify(value, replacer?, space?)) 通常 比规范的字符串转换效果更好:

> console.log(JSON.stringify({ first: 'John', last: 'Doe' }))
{"first":"John","last":"Doe"}
> console.log(JSON.stringify([ 'a', 'b', 'c' ]))
["a","b","c"]

当然,您必须了解 JSON.stringify() 的局限性——它并不总是显示所有内容。例如,它隐藏了其值无法处理的属性(函数等等!)。从好的方面来说,它的输出可以由 eval() 解析,并且它可以将深度嵌套的数据显示为格式良好的树。

陷阱:转换不可逆

考虑到 JavaScript 自动转换的频率,转换并不总是可逆的,这很遗憾,尤其是在布尔值方面:

> String(false)
'false'
> Boolean('false')
true

对于 undefinednull,我们也面临着类似的问题。

比较字符串

有两种比较字符串的方法。 首先,您可以使用比较运算符:<>===<=>=它们有以下缺点:

  • 它们区分大小写

    > 'B' > 'A'  // ok
    true
    > 'B' > 'a'  // should be true
    false
  • 它们不能很好地处理变音符号和重音符号

    > 'ä' < 'b'  // should be true
    false
    > 'é' < 'f'  // should be true
    false

其次,您可以使用 String.prototype.localeCompare(other)它往往表现更好,但并不总是受支持的(有关详细信息,请参阅搜索和比较)。以下是 Firefox 控制台中的交互

> 'B'.localeCompare('A')
2
> 'B'.localeCompare('a')
2

> 'ä'.localeCompare('b')
-2
> 'é'.localeCompare('f')
-2

小于零的结果意味着接收者“小于”参数。大于零的结果意味着接收者“大于”参数。

连接字符串

连接字符串主要有两种方法。

连接:加号 (+) 运算符

只要其操作数之一是字符串,运算符 + 就会执行字符串连接。 如果您想在变量中收集字符串片段,则复合赋值运算符 += 很有用:

> var str = '';
> str += 'Say hello ';
> str += 7;
> str += ' times fast!';
> str
'Say hello 7 times fast!'

连接:连接字符串片段数组

看起来每当将一个片段添加到 str 时,前面的方法都会创建一个新的字符串。较旧的 JavaScript 引擎就是这样做的,这意味着您可以通过首先将所有片段收集在一个数组中,然后在最后一步将它们连接起来,从而提高字符串连接的性能:

> var arr = [];

> arr.push('Say hello ');
> arr.push(7);
> arr.push(' times fast');

> arr.join('')
'Say hello 7 times fast'

但是,较新的引擎通过 + 优化字符串连接,并在内部使用类似的方法。 因此,加号运算符在这些引擎上速度更快。

函数字符串

函数 String 可以通过以下两种方式调用:

String(value)

作为普通函数,它将 value 转换为原始字符串(请参阅 转换为字符串

> String(123)
'123'
> typeof String('abc')  // no change
'string'
new String(str)

作为构造函数,它创建一个新的 String 实例(请参阅 原始值的包装对象),这是一个包装 str 的对象(非字符串被强制转换为字符串)。例如

> typeof new String('abc')
'object'

前一种调用方式比较常见。

字符串构造函数方法

String.fromCharCode(codeUnit1, codeUnit2, ...) 生成一个字符串,其字符是由 16 位无符号整数 codeUnit1codeUnit2 等指定的 UTF-16 代码单元。 例如:

> String.fromCharCode(97, 98, 99)
'abc'

如果要将数字数组转换为字符串,可以通过 apply() 来实现(请参阅 func.apply(thisValue, argArray)

> String.fromCharCode.apply(null, [97, 98, 99])
'abc'

String.fromCharCode() 的反函数是 String.prototype.charCodeAt()

字符串实例属性 length

length 属性指示 字符串中的 JavaScript 字符数,并且是不可变的:

> 'abc'.length
3

字符串原型方法

原始字符串的所有方法都存储在 String.prototype 中(请参阅原始值从包装器借用其方法)。接下来,我将描述它们如何处理原始字符串,而不是 String 的实例。

提取子字符串

以下方法从接收器中提取子字符串

String.prototype.charAt(pos)

返回一个字符串,其中包含位置 pos 处的字符。 例如:

> 'abc'.charAt(1)
'b'

以下两个表达式返回相同的结果,但一些较旧的 JavaScript 引擎仅支持 charAt() 来访问字符

str.charAt(n)
str[n]
String.prototype.charCodeAt(pos)

返回位置 pos 处的 JavaScript 字符(UTF-16 代码单元;请参阅 第 24 章)的代码(16 位无符号整数)。

以下是创建字符代码数组的方法:

> 'abc'.split('').map(function (x) { return x.charCodeAt(0) })
[ 97, 98, 99 ]

charCodeAt() 的反函数是 String.fromCharCode()

String.prototype.slice(start, end?)

返回从位置 start 开始到位置 end(不包括)的子字符串。 这两个参数都可以是负数,然后将字符串的 length 添加到它们中:

> 'abc'.slice(2)
'c'
> 'abc'.slice(1, 2)
'b'
> 'abc'.slice(-2)
'bc'
String.prototype.substring(start, end?)
应避免使用它,而应使用 slice(),它类似,但可以处理负数位置,并且在不同浏览器中的实现更加一致。
String.prototype.split(separator?, limit?)

提取由 separator 分隔的接收器的子字符串,并将它们返回到一个数组中。该方法有两个参数:

  • separator:字符串或正则表达式。如果缺少,则返回完整的字符串,并包装在一个数组中。
  • limit:如果给出,则返回的数组最多包含 limit 个元素。

以下是一些例子

> 'a,  b,c, d'.split(',')  // string
[ 'a', '  b', 'c', ' d' ]
> 'a,  b,c, d'.split(/,/)  // simple regular expression
[ 'a', '  b', 'c', ' d' ]
> 'a,  b,c, d'.split(/, */)   // more complex regular expression
[ 'a', 'b', 'c', 'd' ]
> 'a,  b,c, d'.split(/, */, 2)  // setting a limit
[ 'a', 'b' ]
> 'test'.split()  // no separator provided
[ 'test' ]

如果有一个组,则匹配项也会作为数组元素返回

> 'a,  b  ,  '.split(/(,)/)
[ 'a', ',', '  b  ', ',', '  ' ]
> 'a,  b  ,  '.split(/ *(,) */)
[ 'a', ',', 'b', ',', '' ]

使用 ''(空字符串)作为分隔符来生成一个包含字符串字符的数组

> 'abc'.split('')
[ 'a', 'b', 'c' ]

转换

上一节是关于提取子字符串,而本节是关于将给定的字符串转换为新的字符串。 这些方法的典型用法如下:

var str = str.trim();

换句话说,原始字符串在被(非破坏性地)转换后就被丢弃

String.prototype.trim()

从字符串的开头和结尾删除所有空格:

> '\r\nabc \t'.trim()
'abc'
String.prototype.concat(str1?, str2?, ...)

返回接收器与 str1str2 等的串联:

> 'hello'.concat(' ', 'world', '!')
'hello world!'
String.prototype.toLowerCase()

创建一个新字符串,其中所有原始字符串的字符都转换为小写:

> 'MJÖLNIR'.toLowerCase()
'mjölnir'
String.prototype.toLocaleLowerCase()
toLowerCase() 的工作方式相同,但遵循当前语言环境的规则。根据 ECMAScript 规范:“只有在少数情况下(例如土耳其语),该语言的规则与常规 Unicode 大小写映射冲突时,才会存在差异。”
String.prototype.toUpperCase()

创建一个新字符串,其中所有原始字符串的字符都转换为大写:

> 'mjölnir'.toUpperCase()
'MJÖLNIR'
String.prototype.toLocaleUpperCase()
toUpperCase() 的工作方式相同,但遵循当前语言环境的规则。

搜索和比较

以下方法用于搜索和比较字符串:

String.prototype.indexOf(searchString, position?)

position(默认为 0)开始搜索 searchString。它返回找到 searchString 的位置,如果找不到则返回 -1

> 'aXaX'.indexOf('X')
1
> 'aXaX'.indexOf('X', 2)
3

请注意,在字符串中查找文本时,正则表达式同样有效。例如,以下两个表达式是等效的:

str.indexOf('abc') >= 0
/abc/.test(str)
String.prototype.lastIndexOf(searchString, position?)

position(默认为结尾)开始向后搜索 searchString它返回找到 searchString 的位置,如果找不到则返回 -1:

> 'aXaX'.lastIndexOf('X')
3
> 'aXaX'.lastIndexOf('X', 2)
1
String.prototype.localeCompare(other)

对字符串与 other 执行区分区域设置的比较。它返回一个数字:

  • < 0 如果字符串在 other 之前
  • = 0 如果字符串等效于 other
  • > 0 如果字符串在 other 之后

例如

> 'apple'.localeCompare('banana')
-2
> 'apple'.localeCompare('apple')
0

警告

并非所有 JavaScript 引擎都能正确实现此方法。有些只是将其基于比较运算符。但是,ECMAScript 国际化 API(请参阅ECMAScript 国际化 API)确实提供了一个支持 Unicode 的实现。也就是说,如果该 API 在引擎中可用,则 localeCompare() 将起作用。

如果支持,则 localeCompare() 是比比较运算符更好的比较字符串的选择。有关更多信息,请参阅比较字符串

使用正则表达式进行测试、匹配和替换

以下方法使用正则表达式

String.prototype.search(regexp)(在String.prototype.search:匹配项位于哪个索引?中有更详细的说明)

返回 regexp 在接收器中匹配的第一个索引(如果没有匹配项,则返回 -1):

> '-yy-xxx-y-'.search(/x+/)
4
String.prototype.match(regexp)(在String.prototype.match:捕获组或返回所有匹配的子字符串中有更详细的说明)

将给定的正则表达式与接收器进行匹配。如果未设置 regexp 的标志 /g,则它将返回第一个匹配项的匹配对象:

> '-abb--aaab-'.match(/(a+)b/)
[ 'ab',
  'a',
  index: 1,
  input: '-abb--aaab-' ]

如果设置了标志 /g,则所有完整匹配项(组 0)都将返回到数组中

> '-abb--aaab-'.match(/(a+)b/g)
[ 'ab', 'aaab' ]
String.prototype.replace(search, replacement)(在 String.prototype.replace:搜索和替换中有更详细的说明)

搜索 search 并将其替换为 replacementsearch 可以是字符串或正则表达式,replacement 可以是字符串或函数。除非您使用正则表达式作为 search,并且其标志 /g 已设置,否则只会替换第一个匹配项

> 'iixxxixx'.replace('i', 'o')
'oixxxixx'
> 'iixxxixx'.replace(/i/, 'o')
'oixxxixx'
> 'iixxxixx'.replace(/i/g, 'o')
'ooxxxoxx'

替换字符串中的美元符号 ($) 允许您引用完整匹配项或捕获组:

> 'iixxxixx'.replace(/i+/g, '($&)') // complete match
'(ii)xxx(i)xx'
> 'iixxxixx'.replace(/(i+)/g, '($1)') // group 1
'(ii)xxx(i)xx'

您还可以通过函数计算替换

> function repl(all) { return '('+all.toUpperCase()+')' }
> 'axbbyyxaa'.replace(/a+|b+/g, repl)
'(A)x(BB)yyx(AA)'



[16] 严格来说,JavaScript 字符串由一系列 UTF-16 代码单元组成。也就是说,JavaScript 字符是 Unicode 代码单元(请参阅第 24 章)。

下一页:13. 语句