開發者工具 · URL 編碼器和解碼器
escape() 與encodeURIComponent:JavaScript 的 URL 編碼如何演變
· 背景
JavaScript url 編碼 歷史
JavaScript 已經有了三代 URL 編碼函數,最古老的仍然潛伏在生產程式碼中。這篇文章解釋了 escape() 做錯了什麼,為什麼 ES3 添加了 URI 函數以及它們為什麼保留! * ' ( )。
舊日誌中的 %u20AC — escape() 的明確無誤的指紋及其導致的解碼失敗
舊版 JavaScript 檔案包含使用已棄用的 escape() 函數的 URL 編碼呼叫。日誌檔案或錯誤訊息中的輸出包括序列 %u20AC — 已棄用的 escape() 函數的明確無誤的指紋,沒有其他人使用。此序列與任何標準 URL 編碼都不匹配,基於 RFC 3986 或 WHATWG 規則建構的解碼器將無法識別它。資料無法透過現代工具往返。它是 ES3 之前的常見代碼符號,自 20 世紀 90 年代以來就沒有更新過。
escape() 函數是在 Netscape 時代設計的,當時 JavaScript 還沒有標準或正式的 URL 編碼規則。它使用 %uXXXX 表示法對大多數非 ASCII 字元進行編碼,這是一種沒有其他人使用且沒有任何標準定義的四位十六進位代碼。這對於瀏覽器中的一次性使用是有意義的,但它破壞了與 URL 標準的兼容性,並使資料無法在其他地方解碼。
escape() 和 unescape():Netscape 時代的設計 — 拉丁語 1 假設、%uXXXX 發明以及為什麼它從未符合任何標準
escape() 和 unescape() 假設輸入是 Latin-1 (ISO 8859-1),這是早於 UTF-8 和 Unicode 的字元編碼。它們將每個字符轉換為十六進位代碼,使用 %XX 表示高位拉丁語-1 字符,使用 %uXXXX 表示拉丁語-1 之外的所有字符。像表情符號這樣的非拉丁語 1 字元根本無法表示。這些函數既簡單又快速,但對於任何現代用例來說它們也是完全錯誤的。
這兩個函數都是在標準存在之前加入到 JavaScript 中的。 ES3 在 1999 中引入正確的 URL 編碼後,它們立即被棄用。它們保留在 JavaScript 中是為了向後相容——刪除它們會破壞古老的程式碼。但任何新程式碼都不應該使用它們。它們是遺產。
ES3 (1999) 新增encodeURI 和encodeURIComponent — UTF-8 百分比編碼與 RFC 2396 一致
ES3 引入了兩個函數:encodeURI 和encodeURIComponent。兩者都執行 UTF-8 百分比編碼:將非 ASCII 字元轉換為 UTF-8 位元組,然後將每個位元組寫入 %HH。兩者皆符合當時有效的 RFC 2396。 RFC 3986 後來出現,並沒有改變編碼行為。這些功能至今仍然是標準並應該被使用。
encodeURI 用於編碼完整的 URI; encodeURIComponent 用於對 URI 內的組件進行編碼,例如查詢值或路徑段。這種差異絕對是至關重要的並很容易被誤解。 encodeURI 保留結構字符,例如 : / ? #@=& 和 ;。 encodeURIComponent 對所有這些進行編碼,使它們可以安全地嵌入到更大的 URI 中。
為什麼! * ' ( ) 仍然未編碼 — RFC 2396 「標記」字元在 RFC 3986 移動後被凍結到語言中
這兩個函數都將這些字元保留為未編碼:字母、數字、連字符 (-)、底線 (_)、句點 (.)、波形符 (~) 和五個標點符號! * ' ( )。這些標記來自 RFC 2396,其中將它們列為非保留的「標記」字元。 RFC 3986 在 2005 中出現,並將這五個移到不同的類別,但 JavaScript 已經在 1999 中凍結了encodeURI 和encodeURIComponent。改變他們留下的字元會破壞現有的程式碼,所以他們留下來。
為了向後相容而保留這五個標記未編碼的決定意味著 JavaScript 的編碼並不完全符合 RFC 3986 或 WHATWG 標準。它已經足夠接近實際使用了,現在改變它是完全不可能的。這是 API 穩定性的一個教訓:一旦凍結行為,即使標準不斷發展,也無法更改它。
工作範例:透過轉義、encodeURI 和encodeURIComponent 的相同字串 — 比較三個輸出
採用字串「R&D(研究)=咖啡館」。透過 escape()、encodeURI 和encodeURIComponent 執行它。 escape() 產生“R%26D%20(research)%20%3D%20caf%E9's”,將未編碼的括號和撇號與百分比編碼的“&”號和等號混合在一起。 encodeURI 產生“R&D%20(research)%20=%20caf%C3%A9's”,保留與號和等號,因為它們是結構性的。 encodeURIComponent 產生“R%26D%20%28research%29%20%3D%20caf%C3%A9%27s”,對包括括號和撇號在內的所有內容進行編碼。
將相同的字串貼到URL編碼器和解碼器中,並在encodeURI和encodeURIComponent之間切換以查看差異。然後檢查 escape() 會產生什麼(您可以在瀏覽器控制台中呼叫它,儘管它會警告您)。您立即會發現這三個函數產生三個完全不同的結果。
遷移脫離 escape() — 將舊呼叫映射到正確的現代函數並處理儲存的 %uXXXX 資料
使用 escape() 的舊程式碼必須更新。如果 escape() 用於編碼 URI 元件,請將其替換為encodeURIComponent。如果它用於編碼完整的 URI,請使用encodeURI。對於包含 %uXXXX 序列的儲存資料,您需要一個自訂解碼器:將每個 %uXXXX 轉換為 Unicode 代碼點,然後將代碼點收集到字串中。 JavaScript 的內建 unescape() 將讀取 %uXXXX,但結果可能不正確 UTF-8。
取代 escape() 後,使用包含非 ASCII 字元、標點符號和特殊字元的字串測試碼。現在的輸出應該符合現代工具和標準的預期。如果您的程式碼明顯早於 ES3,它也可能使用其他過時的模式;全面的審計是值得付出努力的。
這不包含的內容 — URL 和 URLSearchParams API,它們將單獨介紹
URL 和 URLSearchParams API(後來新增)為 URL 建構和元件編碼提供了更高層級的介面。它們會自動處理所有轉義並完全符合 WHATWG URL 標準。它們是在現代 JavaScript 中以程式設計方式建立 URL 的首選方法。
這篇文章僅涵蓋編碼函數,而不涉及那些更高層級的 API。 URL 和 URLSearchParams 解析結構,選擇元件規則並序列化結果,而encodeURIComponent 轉換提供的一個字串,而不知道它將被放置在哪裡。這個區別就是邊界:根據舊的 escape() 調用是處理值還是地址來遷移舊的 escape() 調用,然後考慮用結構化 API 替換周圍的手動連接作為單獨的重構。
重點:三個函數,一對倖存的函數 — URL 編碼器和解碼器如何並排顯示現代的encodeURI 和encodeURIComponent 行為
現代 JavaScript 開發應該使用encodeURI 或encodeURIComponent,而不是escape()。這些函數在 1999 中標準化,從那時起就沒有改變。它們將非 ASCII 字元編碼為 UTF-8 位元組並正確處理標準保留字元。 URL 編碼器和解碼器工具實現了這兩種功能,並讓您可以並排查看它們的行為,從而輕鬆地為您的元件選擇正確的功能。
如果您在舊日誌或儲存的資料中遇到 %u 序列,它們是 escape() 輸出,則應該遷移。一旦確定了模式,遷移就很簡單。現代程式碼不應該產生它們。