从 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, 在这块的约束就做得非常好。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342