• 作者:老汪软件技巧
  • 发表时间:2024-09-23 07:00
  • 浏览量:

研究一下JavaScript中字符串的转义规则前言

转义字符是JS的基础知识,按理说没有仔细研究的价值,但前不久我就遇到了一个关于字符串转义的问题,我想了很久才找到答案,于是我决定花时间仔细研究一下关于字符串转义的知识,总结成文章,希望对大家能有所帮助。

字符串字面量中反斜杠\的转义规则

在JS的字符串字面量中,反斜杠\可以对任意的字符串进行转义,其转义规则如下——

特殊字符

如果字符是以下特殊字符之一,则使用反斜杠转义后,会被替换为对应的特殊字符:

八进制转义

如果反斜杠后面跟着的是1个0-7的八进制数字,则会被解释为八进制转义,结果就是八进制数字的字符串形式。例如:

十六进制转义

如果反斜杠后面跟着一个x,然后紧接2个十六进制数字(0-9、A-F、a-f),会被解释为Unicode转义,x后接除此之外的字符会报错。例如:

Unicode 转义

如果反斜杠后面跟着一个u,然后紧接4个十六进制数字(0-9、A-F、a-f),会被解释为 Unicode 转义,u后接除此之外的字符会报错。例如:

其它字符

如果反斜杠后面跟着其它字符,则会被解释为普通字符,加不加\没有区别。例如:

正则表达式的双重转义问题

我们在使用正则表达式时,也会有字符串转义的问题,例如,如果要在正则中匹配字符串?,由于它是正则中的特殊字符,用字面量的方式是这么写的:

var reg = /\?/

而如果我们使用字符串作为RegExp构造函数的参数,由于\是JS字符中的特殊字符,但?不是,因此需要这么写:

var reg = new RegExp("\\?")

而如果遇到字符串和正则都需要转义的情况时,例如反斜杠\,使用RegExp构造函数就必须“双重转义”:

var reg = new RegExp("\\\\")

这种写法非常的不直观,让本就难以阅读的正则变得更难阅读,因此在实际开发中,我们还是尽量使用字面量来创建正则表达式。

但如果是动态的正则就没办法了,对此,MDN上提供了一个解决方案:

function escapeRegExp(string) {
  return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

这个escapeRegExp函数可以将字符串中的特殊字符例如$ ( ) * + . ? [ \ ] ^ { | }进行统一转义,利用它我们可以像正则字面量那样通过构造函数创建正则表达式,而无需额外考虑JS字符串本身的转义:

var reg = new RegExp(escapeRegExp("\\")) // 相当于 /\\/ 或 new RegExp("\\\\")

如何将纯文本的反斜杠变为转义符

这就是我文章开头提到的那个问题,这个需求发生在字符串替换的场景中,例如有这么一篇文章:

曾宴桃源深洞,一曲舞鸾歌凤。长记别伊时,和泪出门相送。如梦,如梦,残月落花烟重。

我需要在所有的句号。后添加一个换行符\n,假设我们是通过输入框的查找、替换来完成操作的:

var text = "曾宴桃源深洞,一曲舞鸾歌凤。长记别伊时,和泪出门相送。如梦,如梦,残月落花烟重。"
// 伪代码
var $search = document.querySelector('#search') // 搜索输入框
var $replacement = document.querySelector('#$replacement') // 替换输入框
var result = text.replace(new RegExp($search.value), $replacement.value) // 替换

由于我们从输入框中拿到的字符串是纯文本的\n,因此像上面这样直接替换的结果就是,\n作为纯文本而不是换行符被添加到了句号后面:

曾宴桃源深洞,一曲舞鸾歌凤。\n长记别伊时,和泪出门相送。\n如梦,如梦,残月落花烟重。\n

显然这不是我们想要的,所以我们不得不先将纯文本的\n替换为换行符:

var result = text.replace(new RegExp($search.value), $replacement.value.replaceAll('\\n', '\n'))

但这显然不是一个好的解决办法,JS中的特殊字符说多不多,但说少也不少,如果每一个都要手动替换,不仅麻烦,性能也堪忧:

$replacement.value
    .replaceAll('\\n', '\n')
    .replaceAll('\\r', '\r')
    .replaceAll('\\t', '\t')
    .replaceAll('\\\\', '\\')
	...

我当时就一直困扰于这个问题,纯文本的反斜杠\相当于字符串字面量的"\\",似乎除了一个个替换,没别的办法能一次性将所有的转义符还原。

但经过一番思考,我最终想到了一个绝妙的解决方案,那就是利用JSON.parse:

function deEscape(str) {
    return JSON.parse(`"${str}"`)
}
var result = text.replace(new RegExp($search.value), deEscape($replacement.value))

deEscape函数可以将纯文本的斜杠\识别为转义符,这其中的原理还真有点“只可意会,不可言传”的味道,不知道看这篇文章的你有没有“品”出来。

如何将反斜杠作为纯文本避免转义

这个倒是比较简单,如果我们希望字符串字面量中的反斜杠\不要作为转义字符,可以使用String.raw,它是个标签模板方法,例如:

var str = String.raw`\n` // 这里的反斜杠\会被解释为普通字符而不是转义字符
console.log(str) // 相当于字面量"\\n"

但要注意的是,不能使用插值语句,例如:

var s1 = '\n'
var str = String.raw`${s1}`
console.log(str) // 相当于字面量"\n",依旧是换行符

因为这里的s1在被赋值之时就已经被解释成换行符了,将换行符插入到模板字符串中,它依旧是换行符。只有在标签模板方法中,在生成字符串的过程中它会被解释为普通字符。