前後端分離架構下,JavaScript 的 escape()、encodeURIComponent() 與 PHP 的 urldecode()、rawurldecode() 編碼規則不一致,常導致中文參數在傳遞過程中產生亂碼或解碼失敗。本文系統梳理編碼差異,提供可直接落地的互轉函式庫。
| 環境 | 函式 | 編碼規則 | 適用場景 |
|---|---|---|---|
| JS | escape() | ASCII 字元不編碼;非 ASCII 轉 %uXXXX (Unicode) 或 %XX (Latin-1) | 已棄用,舊專案維護 |
| JS | encodeURI() | 保留 URL 結構字元 (;/?:@&=+$,#),其餘 UTF-8 編碼為 %XX | 整個 URL 編碼 |
| JS | encodeURIComponent() | 所有非保留字元 UTF-8 編碼為 %XX | 推薦:查詢參數值編碼 |
| PHP | urlencode() | 空格轉 +,其他同 RFC 3986 | 表單提交、查詢字串 |
| PHP | rawurlencode() | 空格轉 %20,嚴格遵循 RFC 3986 | 推薦:REST API、路徑片段 |
%uXXXX PHP 無法原生解碼當前端使用 escape('中文') 產生 %u4E2D%u6587 時,PHP 的 urldecode() 會原樣輸出,因為它不識別 %u 前綴。需自行實作解碼器:
<?php
/**
* 解碼 JavaScript escape() 產生的字串
* @param string $str 含 %uXXXX 或 %XX 格式的字串
* @return string UTF-8 解碼後的字串
*/
function js_unescape($str) {
$ret = '';
$len = strlen($str);
for ($i = 0; $i < $len; $i++) {
if ($str[$i] === '%' && isset($str[$i+1]) && $str[$i+1] === 'u') {
// Unicode 格式 %uXXXX
$val = hexdec(substr($str, $i+2, 4));
$ret .= _utf8_encode_char($val);
$i += 5;
} elseif ($str[$i] === '%' && isset($str[$i+2])) {
// 標準 %XX 格式
$ret .= urldecode(substr($str, $i, 3));
$i += 2;
} else {
$ret .= $str[$i];
}
}
return $ret;
}
function _utf8_encode_char($cp) {
if ($cp < 0x80) return chr($cp);
if ($cp < 0x800) return chr(0xC0 | ($cp >> 6)) . chr(0x80 | ($cp & 0x3F));
if ($cp < 0x10000) return chr(0xE0 | ($cp >> 12)) . chr(0x80 | (($cp >> 6) & 0x3F)) . chr(0x80 | ($cp & 0x3F));
// 補充平面字元(Emoji 等)
return chr(0xF0 | ($cp >> 18)) . chr(0x80 | (($cp >> 12) & 0x3F)) . chr(0x80 | (($cp >> 6) & 0x3F)) . chr(0x80 | ($cp & 0x3F));
}
?>
<?php
// 前端傳遞:escape('PHP開發資源網') → %u5F00%u53D1%u8D44%u6E90%u7F51
$raw = $_GET['p_sort']; // 取得原始編碼字串
$utf8 = js_unescape($raw); // 解碼為 UTF-8
// 若專案內部編碼為 GBK/Big5,需額外轉碼
$final = iconv('UTF-8', 'GBK//IGNORE', $utf8);
echo $final; // 正確顯示:PHP開發資源網
?>
若需在 PHP 端產生給舊版 JS 使用的編碼:
<?php
function php_escape($str, $input_charset = 'GBK') {
$str = iconv($input_charset, 'UCS-2BE', $str); // 轉為 UCS-2 大端序
$out = '';
for ($i = 0; $i < strlen($str); $i += 2) {
$char = substr($str, $i, 2);
$val = unpack('n', $char)[1];
if ($val < 128 && !in_array($val, [37, 64, 35, 43, 38, 61, 59, 58, 44, 36])) {
$out .= chr($val); // ASCII 保留字元不編碼
} else {
$out .= sprintf('%%u%04X', $val);
}
}
return $out;
}
?>
encodeURIComponent() / rawurlencode(),編碼規則一致,無需轉換層。iconv、mb_convert_encoding 呼叫必須明確宣告來源/目標編碼,避免隱性轉換導致資料損毀。掌握編碼細節,才能在中文參數傳遞中徹底杜絕亂碼困擾。