切換語言為:簡體

JavaScript中字串的轉義規則及用法

  • 爱糖宝
  • 2024-09-22
  • 2044
  • 0
  • 0

前言

跳脫字元是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

正規表示式的雙重轉義問題

我們在使用正規表示式時,也會有字串轉義的問題,例如,如果要在正則中匹配字串?,由於它是正則中的特殊字元,用字面量的方式是這麼寫的:

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,假設我們是透過輸入框的查詢、替換來完成操作的:

JavaScript中字串的轉義規則及用法

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在被賦值之時就已經被解釋成換行符了,將換行符插入到模板字串中,它依舊是換行符。只有在標籤模板方法中,在生成字串的過程中它會被解釋為普通字元。

0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.