- 作者:老汪软件技巧
- 发表时间: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在被赋值之时就已经被解释成换行符了,将换行符插入到模板字符串中,它依旧是换行符。只有在标签模板方法中,在生成字符串的过程中它会被解释为普通字符。