從 RegExp 構造器看 JS 字符串轉義設計

多年前我第一次入職騰訊的時候,DC 從杭州給我寄來了一本他剛翻譯出爐的《高性能 JavaScript》。那段時間爲了幫忙校對,我仔細閱讀了書中的每個段落,結果積累了很多 JavaScript 基礎知識。如今還依稀記得書中提到的幾個知識點: IE7 瀏覽器在大字符串處理時的極致性能優化;位運算符用於 config 配置的各類 trick;以及今天想聊的 RegExp 構造器的第一個參數設計問題。php

上週接到一個需求,根據頁面 url 來決定是否出現一個彈窗提示。爲了方便管理這個特性,我將 url 列表配置在了後臺,前端經過接口取得列表再進行校驗。html

其中有一條規則是「全部機構首頁須要彈窗」,由於機構會有本身的獨立二級域名,因此這裏必需要用到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 參數須要轉義的知識點,其實基本用不到。git

 

一、經過接口返回的字符串在變量賦值時無需轉義

前端 AJAX 請求取到的接口數據必定是 string 類型的,這種未經過字符串字面量形式賦值給變量時是無需轉義的。以 fetchAPI 爲例:github

// 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 參數轉義問題。json

 

二、表單輸入項的字符串賦值給變量時也無需轉義

假設頁面中存在輸入框 <input id="test"> ,在輸入框中輸入字符 \w+\.ke\.qq\.com,則經過 JS 獲取到的值能夠直接傳入 RegExp 構造器,一樣無需考慮轉義問題。後端

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

 

由於表單項中的字符串也是直接賦值,而非經過引號字面量的字符串定義方式賦值。瀏覽器

 

三、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,在這塊的約束就作得很是好。

相關文章
相關標籤/搜索