研究一下JavaScript中字符串的转义

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

前言

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

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

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

  1. 特殊字符

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

    • \ 本身 -> \

    • ' 单引号 -> '

    • " 双引号 -> "

    • 反引号 -\>

    • n 换行符 -> \n (U+000A)

    • r 回车符 -> \r (U+000D)

    • t 制表符 -> \t (U+0009)

    • b 退格符 -> \b (U+0008)

    • f 换页符 -> \f (U+000C)

    • v 垂直制表符 -> \v (U+000B)

  2. 八进制转义

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

    • \0 -> \x00
    • \7 -> \x07
    • \8 -> 8 超过7就不再转义
  3. 十六进制转义

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

    • \x41 -> A (U+0041)
    • \x7A -> z (U+007A)
    • \xhi -> 报错
  4. Unicode 转义

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

    • \u0041 -> A (U+0041)
    • \u007A -> z (U+007A)
    • \uhijk -> 报错
  5. 其它字符

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

    • \a -> a
    • \B -> B

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

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

js 复制代码
var reg = /\?/

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

js 复制代码
var reg = new RegExp("\\?")

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

js 复制代码
var reg = new RegExp("\\\\")

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

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

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

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

js 复制代码
var reg = new RegExp(escapeRegExp("\\")) // 相当于 /\\/ 或 new RegExp("\\\\")

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

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

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

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

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

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

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

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

js 复制代码
var result = text.replace(new RegExp($search.value), $replacement.value.replaceAll('\\n', '\n'))

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

js 复制代码
$replacement.value
    .replaceAll('\\n', '\n')
    .replaceAll('\\r', '\r')
    .replaceAll('\\t', '\t')
    .replaceAll('\\\\', '\\')
	...

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

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

js 复制代码
function deEscape(str) {
    return JSON.parse(`"${str}"`)
}

var result = text.replace(new RegExp($search.value), deEscape($replacement.value))

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

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

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

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

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

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

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

相关推荐
啧不应该啊6 分钟前
vue配置axios
前端·javascript·vue.js
__fuys__10 分钟前
【HTML样式】加载动画专题 每周更新
前端·javascript·html
Want59513 分钟前
HTML粉色烟花秀
前端·css·html
让开,我要吃人了18 分钟前
HarmonyOS鸿蒙开发实战(5.0)自定义全局弹窗实践
前端·华为·移动开发·harmonyos·鸿蒙·鸿蒙系统·鸿蒙开发
一条晒干的咸魚36 分钟前
响应式CSS 媒体查询——WEB开发系列39
前端·css·html·css3·响应式设计·媒体查询
凌晨五点的星1 小时前
网络安全-webshell绕过,hash碰撞,webshell绕过原理
开发语言·前端·javascript
天心天地生1 小时前
【bugfix】-洽谈回填的图片消息无法显示
开发语言·前端·javascript
啧不应该啊1 小时前
element plus 按需导入vue
前端·javascript·vue.js
Gungnirss1 小时前
vue中提示Parsing error: No Babel config file detected
前端·vue.js·ubuntu
梅秃头2 小时前
vue2+elementUI实现handleSelectionChange批量删除-前后端
前端·javascript·elementui