从 RegExp 构造器看 JS 字符串转义设计

多年前我第一次入职腾讯的时候,DC 从杭州给我寄来了一本他刚翻译出炉的《高性能 JavaScript》。那段时间为了帮忙校对,我仔细阅读了书中的每一个段落,结果积累了不少 JavaScript 基础知识。现在还依稀记得书中提到的几个知识点: IE7 浏览器在大字符串处理时的极致性能优化;位运算符用于 config 配置的各种 trick;以及今天想聊的 RegExp 构造器的第一个参数设计问题。

上周接到一个需求,根据页面 url 来决定是否出现一个弹窗提示。为了方便管理这个特性,我将 url 列表配置在了后台,前端通过接口取得列表再进行校验。

其中有一条规则是「所有机构首页需要弹窗」,因为机构会有自己的独立二级域名,所以这里必须要用到location.host 对应的正则表达式 \w+\.ke\.qq\.com

new RegExp(/\w+\.ke\.qq\.com/).test('ktmaster.ke.qq.com') // 返回 true

// 由于正则表达式字符串是 cgi 接口中返回的,所以第一个参数只能用 string 类型
// 而 RegExp 构造器使用 string 参数时,其中的 \w、\ 等特殊含义字符是需要使用反斜杠再做一层转义,这样同时导致正则语义变得很不清晰
new RegExp('\w+\.ke\.qq\.com').test('ktmaster.ke.qq.com')  // 返回 false
new RegExp('\\w+\\.ke\\.qq\\.com').test('ktmaster.ke.qq.com')  // 返回 true

然而,需求真正落地实现后发现:RegExp 构造器 string 参数需要转义的知识点,其实基本用不到。

1、通过接口返回的字符串在变量赋值时无需转义

前端 AJAX 请求取到的接口数据一定是 string 类型的,这种未通过字符串字面量形式赋值给变量时是无需转义的。以 fetchAPI 为例:

// 1. 其中 data 接口返回的内容是 \w+\.ke\.qq\.com
fetch('/data')
  .then(res => res.text())
  .then(resText => {
    console.log(new RegExp(resText)) // 正确实例化了 /\w+\.ke\.qq\.com/
  })

// 2. 字面量形式定义的字符串不转义,会与期望不符
const regText = '\w+\.ke\.qq\.com' // 字符串定义时 \ 会与后面一个字符合并解析掉
console.log(regText === 'w+.ke.qq.com') // 返回 true
console.log(new RegExp(regText)) // 返回的是 /w+.ke.qq.com/

现在大部分的接口数据会使用 JSON string,接口返回后通过 JSON.parse 成 JavaScript Object ,再通过 key 来取值。而对于 JSON 数据来说,后端 JSON.stringify 时,\ 字符是一定会经过一层转义的(这样才符合 JSON 规范)。以 PHP 为例:

<?php
$regText = '\w+\.ke\.qq\.com'; // 注意 PHP 中单引号内的字符串不会经过解析
echo json_encode(array('pattern' => $regText));
// 返回的是 {"pattern":"\\w+\\.ke\\.qq\\.com"}

所以接口场景下,同样不存在 RegExp 构造器的 string 参数转义问题。

2、表单输入项的字符串赋值给变量时也无需转义

假设页面中存在输入框 <input id="test"> ,在输入框中输入字符 \w+\.ke\.qq\.com,则通过 JS 获取到的值可以直接传入 RegExp 构造器,同样无需考虑转义问题。

const regText = document.getElementById('test').value
new RegExp(regText) // 返回 /\w+\.ke\.qq\.com/

因为表单项中的字符串也是直接赋值,而非通过引号字面量的字符串定义方式赋值。

3、JS 代码中的转义处理

另外一种可能用到 RegExp string 参数的场景是:基于 JS 逻辑,动态创建正则表达式。例如正则表达式 /\w{3}/ 中的数字 3,是通过某个变量来传递的。那么在写正则时需要写成:

let n = 3
new RegExp('\\w{' + n + '}') // 这里的 \w 为特殊字符,需要经过 \ 转义

Python 语言中是通过 raw string 修饰符来解决字符串转义问题,在字符串前加上 r 标记,表示这个字符串的内容不经过解析。即 print r'\n' == '\\n' 返回 True。

为了解决模板字符串的解析和转义问题,ES6 模板字面量中引入了反引号(`)和 tag function(知名「CSS in JS」 库 styled-components 中大量使用了这种语法)。这里的场景就可以写成十分类似 Python 的风格,当需要转义的内容比较多时,能保持较好的正则表达式语义:

const r = String.raw
let n = 3
new RegExp(r`\w{${n}}`)

不过这种使用场景十分罕见,我至今还没有遇到过。

回过头来看,JS 正则表达式构造器的参数设计问题,其实不是 RegExp 引起的,而是 JavaScript String 的设计缺陷:单引号和双引号非但没有参考 PHP/Shell 之类的设计,反而给前端社区留下「应该使用单引号还是双引号」的代码风格争论。反观 Golang, 在这块的约束就做得非常好。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏python3

python3--字典,字典的嵌套,概念:分别赋值

  字典是python中唯一的映射类型,采用键值对(key-value)的形式存储数据。python对key进行哈希函数运算,根据计算的结果决定value的存储...

2913
来自专栏程序员的知识天地

JavaScript 开发人员需要知道的简写技巧

本文来源于多年的 JavaScript 编码技术经验,适合所有正在使用 JavaScript 编程的开发人员阅读。

701
来自专栏JackieZheng

初探JavaScript(一)——也谈元素节点、属性节点、文本节点

  Javascript大行其道的时候,怎么能少了我来凑凑热闹^_^   基本上自己对于js的知识储备很少,先前有用过JQuery实现一些简单功能,要论起JS的...

2267
来自专栏Web项目聚集地

前端面试送命题-JS三座大山

首先创造空的对象,再让this指向这个对象,通过this.name进行赋值,最终返回this,这其实也是new 一个对象的过程。

1103
来自专栏老马寒门IT

前端面试题:JS中的let和var的区别

最近很多前端的朋友去面试被问到let和var的区别,其实阮一峰老师的ES6中已经很详细介绍了let的用法和var的区别。我简单总结一下,以便各位以后面试中使用。...

38310
来自专栏码洞

《快学 Go 语言》第 6 课 —— 字典

字典在数学上的词汇是映射,将一个集合中的所有元素关联到另一个集合中的部分或全部元素,并且只能是一一映射或者多对一映射。

742
来自专栏web前端教室

ES6中提升效率的新方法,多学一点是一点。

ES6,ECMAScript6是目前js的新标准,又说是现在的es6是2015年发出来的,所以又称它为ECMAScript2015,所以说es6就是es2015...

2035
来自专栏Fundebug

4个错误使用JavaScript数组方法的案例

为了保证可读性,本文采用意译而非直译。另外,本文版权归原作者所有,翻译仅用于学习。

1194
来自专栏程序员八阿哥

王老板Python面试(7):年薪20万的阿里巴巴Python工程师面试题

随着人工智能时代的到来,Python工程师的需求量也在与日俱增。现在,很多知名公司都或多或少的在使用Python。而对于Python工程师来说,进入大公司,是一...

1141
来自专栏IMWeb前端团队

相爱相杀——正则与浏览器间的爱恨情仇

在腾爷《作为一个前端,可以如何机智地弄坏一台电脑?》之后,我就觉得需要学习这种低调奢华有内涵的文(biao)章(ti)名(dang)。 嘿嘿嘿,你看,你被我骗进...

2270

扫码关注云+社区

领取腾讯云代金券