前端用 escape('腳本之家') 得到 %u811A%u672C%u4E4B%u5BB6,PHP urldecode 無法識別 %uXXXX 格式。本文提供完整反解函數、編碼原理分析、常見編碼範圍對照表,解決前後端編碼不互通問題。
<a href="" onclick="window.open('list.php?kw=' + escape('腳本之家'))">點擊</a>
// 實際請求:list.php?kw=%u811A%u672C%u4E4B%u5BB6
// PHP urldecode($_GET['kw']) → 仍為 %u811A... 無法解碼
<?php
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') {
// %uXXXX (Unicode)
$val = hexdec(substr($str, $i+2, 4));
if ($val < 0x7f) {
$ret .= chr($val);
} elseif ($val < 0x800) {
$ret .= chr(0xc0 | ($val >> 6)) . chr(0x80 | ($val & 0x3f));
} else {
$ret .= chr(0xe0 | ($val >> 12)) .
chr(0x80 | (($val >> 6) & 0x3f)) .
chr(0x80 | ($val & 0x3f));
}
$i += 5;
} elseif ($str[$i] === '%') {
// %XX (ASCII)
$ret .= urldecode(substr($str, $i, 3));
$i += 2;
} else {
$ret .= $str[$i];
}
}
return $ret;
}
// 使用:注意編碼轉換
$kw = js_unescape($_REQUEST['kw']); // 得到 UTF-8
$kw = iconv('UTF-8', 'GBK//IGNORE', $kw); // 若項目為 GBK,需轉碼
echo $kw; // 輸出:腳本之家
?>
<?php
function phpescape($str) {
$len = strlen($str);
$out = '';
for ($i = 0; $i < $len; $i++) {
if (ord($str[$i]) >= 127) { // 多字節字符(中文)
// 取雙字節(GBK 編碼下一個漢字 2 字節)
$char = substr($str, $i, 2);
$ucs2 = bin2hex(iconv('GBK', 'UCS-2', $char));
$out .= '%u' . $ucs2;
$i++; // 跳過第二字節
} else { // ASCII
$out .= '%' . dechex(ord($str[$i]));
}
}
return $out;
}
// 更通用的 UTF-8 版本
function escape_utf8($str) {
return preg_replace_callback('/./u', function($m) {
$cp = mb_ord($m[0], 'UTF-8');
return $cp < 128 ? '%' . strtoupper(dechex($cp)) : '%u' . strtoupper(str_pad(dechex($cp), 4, '0', STR_PAD_LEFT));
}, $str);
}
?>
常見開源實現:
<?php
function phpEscape($str) {
// 正則:匹配「雙字節字符(含中文)」或「連續 ASCII」
preg_match_all('/[\x80-\xff].|[\x01-\x7f]+/', $str, $r);
$ar = $r[0];
foreach ($ar as $k => $v) {
if (ord($v[0]) < 128) {
$ar[$k] = rawurlencode($v); // ASCII 直接 urlencode
} else {
$ar[$k] = '%u' . bin2hex(iconv('GB2312', 'UCS-2', $v)); // 中文轉 UCS-2
}
}
return join('', $ar);
}
?>
正則詳解:
[\x80-\xff].:匹配首字節在 0x80-0xFF 範圍的雙字節字符(GBK 中文首字節範圍)。[\x01-\x7f]+:匹配連續 ASCII 字符(0x01-0x7F)。preg_match_all 將字符串完整切分。| 編碼/語言 | 十六進制範圍 | 正則寫法 (PCRE) |
|---|---|---|
| GBK 雙字節整體 | 0x00 - 0xFF | [\x00-\xff] |
| ASCII | 0x20 - 0x7F | [\x20-\x7f] |
| GBK 中文區 | 0xA1 - 0xFF | [\xa1-\xff] 或 [\x80-\xff] |
| UTF-8 中文 (基本區) | U+4E00 - U+9FA5 | [\x{4e00}-\x{9fa5}] (需 u 修飾符) |
| 韓文 | U+3130-U+318F, U+AC00-U+D7A3 | [\x{3130}-\x{318f}\x{ac00}-\x{d7a3}] |
| 日文 | U+0800-U+4E00 | [\x{0800}-\x{4e00}] |
// JS 原生 unescape 已棄用,現代瀏覽器仍支持但建議遷移
// 推薦:前後端統一用 encodeURIComponent / decodeURIComponent (UTF-8)
// 或 JSON 傳輸,避免手動編碼
encodeURIComponent (JS) + urldecode (PHP),均基於 UTF-8,無兼容性問題。js_unescape 反解,並統一項目編碼為 UTF-8,省去 iconv 麻煩。json_encode,Header 設置 Content-Type: application/json; charset=utf-8,最省心。