DevTool 團隊

URL 參數編碼怎麼選:component、URI 與表單三種模式的判斷

URL 參數裡帶中文就報錯、帶 & 就被截斷?問題不在字元,在沒選對編碼方式。這篇按「你要編碼的是什麼」分流:編碼參數值用 encodeURIComponent(& = ? # 全部轉義),編碼整條 URL 用 encodeURI(保留查詢結構),處理表單提交體用表單模式(空格變 +)。講清 %E4%BD%A0 這串東西的來歷(UTF-8 三位元組的百分號表示)、+ 與 %20 的愛恨情仇(表單編碼歷史包袱怎麼製造簽名校驗事故)、雙重編碼與 Invalid URI 報錯的排查;最後是真話:編碼解決傳輸安全,不解決參數語意。

先分流:你要編碼的是什麼

「URL 編碼」這個搜尋詞背後藏著三種不同的活,選錯模式就是事故的開始:

你要編碼的 用什麼模式 為什麼
參數值(放進 ?key= 後面的內容) 元件編碼(encodeURIComponent) 值裡的 & = ? # 必須轉義,否則 a=1&b=2 裡的 & 會被當成參數分隔符
整條 URL(已有 ? 與 & 結構,只想讓中文空格合法化) URI 編碼(encodeURI) 保留 URL 結構字元,只編碼不合法的字元
表單提交體(application/x-www-form-urlencoded) 表單編碼 空格編碼為 + 而不是 %20——歷史包袱,詳見下文

三種模式(URL 編碼解碼器 都支援,切換即對比):

輸入:       a=hello world&next=你
component:  a%3Dhello%20world%26next%3D%E4%BD%A0   (& = 全轉義,當資料傳)
uri:        a=hello%20world&next=%E4%BD%A0          (結構保留,只編碼非法字元)
form:       a%3Dhello+world%26next%3D%E4%BD%A0      (空格變 +,表單約定)

一句話記法:編碼的是「資料」用 component,編碼的是「位址」用 uri,走的是「表單」用 form。

為什麼參數必須編碼

不編碼不是「有時候能跑」,是埋雷。四個事故場景:

  • & 截斷參數:值裡出現 &,伺服器把它當下一個參數的開始——q=a&b 傳過去變成兩個參數,前者被截斷;
  • # 截斷查詢:# 之後的部分是片段識別符,根本不會發到伺服器——值裡的 # 會讓後面整段消失;
  • 空格與中文:部分瀏覽器會兜底編碼,但「部分兜底」意味著行為不可預期,跨端不一致;
  • % 本身:值裡的 % 不編碼,解碼端把它當轉義引導符,遇 %GG 這類非法序列直接報錯。

規範的態度很簡單:URL 裡只保證一部分 ASCII 安全,其餘一律百分號編碼。%E4%BD%A0 就是「你」——UTF-8 三個位元組 E4 BD A0,每位元組寫成 % 加兩位十六進位。它不是亂碼也不是加密,是同一內容的合法寫法,一個漢字對應三組 %XX 是 UTF-8 的特徵。

+ 與 %20:一個歷史包袱的兩面

空格有兩種編碼:%20(百分號編碼,規範路徑)和 +(表單編碼,HTML 表單時代的歷史約定,沿用至今)。

事故的典型形態:值裡本來有 +——電話號碼 +86、公式 a+b——伺服器按表單規則解碼,把 + 還原成空格:+8613800000000 變成 8613800000000。更隱蔽的是簽名場景:客戶端用 %20 編碼空格、服務端按表單規則處理成 +,簽名串對不上,校驗失敗——你以為金鑰錯了,其實是空格打架。

排查方法:看請求的 Content-Type——application/x-www-form-urlencoded 意味著走 + 約定,JSON body(application/json)裡空格就是空格,不參與這套規則。解法:編碼端把字面 + 編成 %2B;空格表示兩端統一(都用 %20 或都按表單約定)。帶表單模式的編碼工具可以直接驗證兩端行為是否一致。

排查清單:解碼失敗與雙重編碼

  • Invalid URI 報錯:% 後面不是兩位十六進位(%GG、孤立 %、被截斷的 %E4%BD 三缺一)都會讓 decodeURIComponent 拋錯。先搜孤立 % 判斷是否截斷;
  • 雙重編碼:% 被編碼成 %25,於是 %E4 變成 %25E4——看到 %25 開頭的串,先再解一層。成因多是框架編了一次、程式碼裡又手動編了一次;
  • 解了一層還是亂碼:數一下一個漢字對應幾組 %XX——三組是 UTF-8 正常態,還亂碼說明內容另有編碼層(比如先 Base64 再 URL,逐層剝);
  • 整條 URL 解碼後結構壞了:對整條 URL 用 component 模式解碼,& = 還原成資料後參數結構就亂了——解碼模式也要與編碼時一致。

真話:編碼解決傳輸,不解決語意

百分號編碼保證的是「這段內容能安全通過 URL 通道」,它不解決參數語意問題:重複 key(a=1&a=2 誰生效是伺服器的事)、陣列參數的序列化格式(a[]=1 vs a=1,2 vs a=1&a=2,是框架約定)、空值與 null 的表示——這些用編碼工具折騰不出來答案,去查你的框架文件。工具的領地是編碼層的排錯:把手裡這串東西編對、解對、看清裡面是什麼。

常見問題

encodeURIComponent 和 encodeURI 有什麼區別,各自什麼時候用?

區別在對待 URL 結構字元的態度:encodeURIComponent 把 & = ? # / 全部轉義——它假設你編碼的是「一段資料」;encodeURI 保留這些結構字元——它假設你編碼的是「一條完整的 URL」。編碼參數值必須用 encodeURIComponent,否則值裡的 & 會被伺服器當成下一個參數的分隔符;編碼整條已有結構的 URL 才用 encodeURI。用反了:前者會把 URL 攔腰打斷,後者會讓參數值被截斷。

URL 參數裡的中文為什麼變成了 %E4%BD%A0 這樣的串?

這是百分號編碼:URL 規範裡只允許一部分 ASCII 字元直接出現,其他內容必須編碼。中文字元先按 UTF-8 轉成位元組——「你」是三個位元組 E4 BD A0——每個位元組再寫成百分號加兩位十六進位,就成了 %E4%BD%A0。它不是加密也不是損壞,是同一內容的另一種寫法;解碼工具一步還原。一個漢字對應三組 %XX 是 UTF-8 的特徵。

加號解碼後為什麼變成了空格?

這是表單編碼的歷史包袱:HTML 表單提交時空格被編碼成 + 而不是 %20,這個約定沿用至今。值裡本來就有 +(電話號碼 +86),伺服器按表單規則解碼就把 + 還原成了空格——簽名校驗對不上、資料損壞都是這麼來的。排查方法:看 Content-Type 是不是 x-www-form-urlencoded;解法是編碼端把 + 編成 %2B,或兩端統一用 %20 表示空格。

解碼時報 Invalid URI 錯誤怎麼回事?

decodeURIComponent 遇到非法百分號序列會直接拋錯——% 後面不是兩位十六進位(%GG、孤立的 %、被截斷的序列)。常見原因三個:編碼層數不對(雙重編碼只解了一次);內容裡有原始 % 字元沒被編碼成 %25;複製貼上時編碼串被截斷。排查順序:先搜孤立 % 看是否截斷,再試再解一次看是否雙重編碼。