前言
跳脫字元是JS的基礎知識,按理說沒有仔細研究的價值,但前不久我就遇到了一個關於字串轉義的問題,我想了很久才找到答案,於是我決定花時間仔細研究一下關於字串轉義的知識,總結成文章,希望對大家能有所幫助。
字串字面量中反斜槓\的轉義規則
在JS的字串字面量中,反斜槓\
可以對任意的字串進行轉義,其轉義規則如下——
特殊字元
如果字元是以下特殊字元之一,則使用反斜槓轉義後,會被替換為對應的特殊字元:
\
本身 ->\
'
單引號 ->'
"
雙引號 ->"
`
反引號 ->`
n
換行符 ->\n
(U+000A)r
回車符 ->\r
(U+000D)t
製表符 ->\t
(U+0009)b
退格符 ->\b
(U+0008)f
換頁符 ->\f
(U+000C)v
垂直製表符 ->\v
(U+000B)八進制轉義
如果反斜槓後面跟著的是1個0-7的八進制數字,則會被解釋為八進制轉義,結果就是八進制數字的字串形式。例如:
\0
->\x00
\7
->\x07
\8
->8
超過7就不再轉義十六進制轉義
如果反斜槓後面跟著一個
x
,然後緊接2個十六進制數字(0-9、A-F、a-f),會被解釋為Unicode轉義,x
後接除此之外的字元會報錯。例如:\x41
->A
(U+0041)\x7A
->z
(U+007A)\xhi
-> 報錯Unicode 轉義
如果反斜槓後面跟著一個
u
,然後緊接4個十六進制數字(0-9、A-F、a-f),會被解釋為 Unicode 轉義,u
後接除此之外的字元會報錯。例如:\u0041
->A
(U+0041)\u007A
->z
(U+007A)\uhijk
-> 報錯其它字元
如果反斜槓後面跟著其它字元,則會被解釋為普通字元,加不加
\
沒有區別。例如:\a
->a
\B
->B
正規表示式的雙重轉義問題
我們在使用正規表示式時,也會有字串轉義的問題,例如,如果要在正則中匹配字串?
,由於它是正則中的特殊字元,用字面量的方式是這麼寫的:
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在被賦值之時就已經被解釋成換行符了,將換行符插入到模板字串中,它依舊是換行符。只有在標籤模板方法中,在生成字串的過程中它會被解釋為普通字元。