写字符串的三种方式
JavaScript 提供了三种字符串定界符。其中两种可以互换,第三种能做更多事。
单引号和双引号生成的字符串完全相同,选一种并坚持下去就好(大多数现代代码库默认用单引号,或者交给格式化工具决定)。反引号生成的是模板字面量,有趣的地方就在这里:它支持插值,还能跨越多行。
在底层,三种写法生成的都是同一种原始字符串类型。定界符只是语法上的选择。
转义序列与内嵌引号
在字符串中,反斜杠表示转义序列的开始。常见的有 \n(换行)、\t(制表符)、\\(反斜杠本身),以及用来嵌入与开头相同引号的 \" / \':
换用另一种引号,就能避免大部分转义:'She said "hi" and left.' 不需要任何反斜杠。务实比教条更重要。
模板字面量:反引号的超能力
模板字面量能做两件普通字符串做不到的事。第一,它可以用 ${...} 插入表达式:
${...} 里面的任何内容都是 JavaScript 表达式,而不只是变量名。算术运算、函数调用、三元表达式,甚至其他模板字面量,都可以:
第二,模板字面量可以跨越多行,无需 \n:
源代码中的换行会成为字符串中的换行。只要比一个简短的标签长,反引号几乎总是正确的选择。
插值胜过拼接
在模板字面量出现之前,构造动态字符串的唯一方法是 + 运算符:
两者的输出完全相同。模板字面量读起来就像它要构造的那句话,而 + 版本迫使你盯着引号、空格和拼接运算符。只有在往已有字符串末尾追加一小段内容时才用 +。更长的内容,就用反引号。
字符串是不可变的
每个看起来在修改字符串的方法,实际上都会返回一个新字符串:
如果你想要修改后的结果,就必须把它赋给某个变量。初学者常在这里栽跟头:写了 s.replace("a", "b"),以为 s 会改变。它不会。JavaScript 中的字符串和数字一样:值本身无法被修改,只能让变量指向一个新值。
你真正会用到的字符串方法
完整的字符串 API 很庞大。你每天会用到的就这几个:
几点值得记住:
length是属性,不是方法,不加括号。replace配合普通字符串只替换第一个匹配。要替换所有匹配,用replaceAll或带g标志的正则表达式。slice(start, end)接收一个左闭右开的范围:end位置的字符不包含在内。slice的负数索引从末尾开始算:s.slice(-3)得到最后三个字符。
索引与遍历字符
字符串就像一个只读的字符数组。你可以按索引访问、可以遍历,但不能给某个位置赋值:
for...of 按码点遍历,能正确处理大多数 Unicode 字符。用索引(s[i])遍历的是 UTF-16 码元,可能会把 emoji 和其他非 BMP 字符劈成两半。普通文本两种方式都没问题;对于用户输入的内容,优先使用 for...of 或 [...s]。
标签模板字面量
还有一种形式值得了解,即使你不常自己写。标签模板字面量会调用一个函数,把静态的字符串片段和插值的值作为单独的参数传进去:
标签函数决定最终字符串的样子:它可以转义 HTML、安全地构造 SQL、编译 CSS,或者解析 GraphQL。你在库中(styled-components、graphql-tag、lit-html)看到这种写法的机会,远比自己写的机会多。但当你确实需要自定义字符串处理时,标签是最干净的工具。
常见陷阱
几个容易踩的坑:
- 忘了把方法的结果赋值。 单独写
s.trim()什么也不会改变。 - 用
+拼接数字和字符串。"Age: " + 30 + 5的结果是"Age: 305",而不是"Age: 35"。模板字面量能避开这个问题:`Age: ${30 + 5}`。 - 混淆
==和字符串比较。 后面会专门讲相等性,但"1" == 1是true,而"1" === 1是false。优先使用===。 - 以为
length等于字符个数。 对于 emoji 和其他代理对字符,"🙂".length是 2,而不是 1。需要用户感知的字符数时,用[..."🙂"].length或Array.from(s).length。
下一篇:数字与 BigInt
字符串是一种原始类型,数字是另一种,而 JavaScript 的数字系统有它自己的怪癖:整数和浮点数共用一个 Number 类型,不够用时还有 BigInt。这就是下一页的内容。
常见问题
JavaScript 中引号和反引号有什么区别?
单引号(')和双引号(")创建的都是普通字符串,两者可以互换。反引号(`)创建的是模板字面量,支持用 ${...} 插值,还能跨越多行而无需转义字符。只要你要插入变量或写多行文本,就用反引号。
JavaScript 中怎样把变量插入字符串?
使用模板字面量:用反引号包住字符串,把表达式放进 ${...} 中。例如 `Hello, ${name}!` 会计算 name,再把结果拼接进去。里面可以放任何表达式:${a + b}、${user.name.toUpperCase()},甚至嵌套的模板字面量。
JavaScript 的字符串是不可变的吗?
是的。每个字符串方法(toUpperCase、slice、replace 等)都会返回一个新字符串,而不是修改原字符串。如果你写了 s.toUpperCase() 却没把结果赋给任何变量,什么都不会改变。这和 Python 或 Java 中字符串的模型一样。
什么是标签模板字面量?
标签模板字面量会调用一个函数,把模板中的字符串片段和插值的值作为单独的参数传进去:tag`Hello, ${name}` 会调用 tag(["Hello, ", ""], name)。styled-components 和 graphql-tag 等库就是用标签在生成最终值之前解析或转换模板的。