PHP 實現 JavaScript escape() 編碼函數:Unicode 轉義、UTF-8 相容、純 PHP 無依賴
JS escape() (已棄用但存量代碼廣泛使用) 將字串編碼為 %XX (Latin-1) 或 %uXXXX (Unicode)。PHP 無原生對應函數,本文提供純 PHP 實現,支援 UTF-8 輸入、正確處理多字節字符、輸出與 JS escape() 完全一致的字串,並對比 encodeURIComponent 現代替代方案。
一、escape() 編碼規則回顧
| 字符範圍 | 編碼輸出 | 範例 |
ASCII 字母數字 * @ - _ + . / | 不編碼 | a → a |
| ASCII 其餘 (空格、標點、控制字符) | %XX (十六進位) | 空格 → %20 |
| 非 ASCII (Unicode > U+00FF) | %uXXXX (4位十六進位) | 中 → %u4E2D |
二、純 PHP 實現 (UTF-8 安全)
<?php
/**
* 模擬 JavaScript escape() 編碼
* @param string $string UTF-8 編碼字串
* @return string escape() 風格編碼字串
*/
function jsEscape(string $string): string
{
$result = '';
$length = strlen($string);
$safeChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789*@-_+./';
for ($i = 0; $i < $length; ) {
$byte = ord($string[$i]);
// 1. 單字節 ASCII
if ($byte <= 0x7F) {
$char = $string[$i];
if (strpos($safeChars, $char) !== false) {
$result .= $char;
} else {
$result .= '%' . strtoupper(dechex($byte));
}
$i++;
continue;
}
// 2. 多字節 UTF-8 序列解碼
$bytes = 0;
if (($byte & 0xE0) === 0xC0) $bytes = 2; // 110xxxxx
elseif (($byte & 0xF0) === 0xE0) $bytes = 3; // 1110xxxx
elseif (($byte & 0xF8) === 0xF0) $bytes = 4; // 11110xxx
else { $i++; continue; } // 非法序列跳過
// 邊界檢查
if ($i + $bytes > $length) { $i++; continue; }
$seq = substr($string, $i, $bytes);
$i += $bytes;
// 解碼為 Unicode 碼點
$codepoint = 0;
if ($bytes === 2) {
$codepoint = (($seq[0] & 0x1F) << 6) | ($seq[1] & 0x3F);
} elseif ($bytes === 3) {
$codepoint = (($seq[0] & 0x0F) << 12) | (($seq[1] & 0x3F) << 6) | ($seq[2] & 0x3F);
} elseif ($bytes === 4) {
$codepoint = (($seq[0] & 0x07) << 18) | (($seq[1] & 0x3F) << 12) | (($seq[2] & 0x3F) << 6) | ($seq[3] & 0x3F);
}
// 3. 輸出編碼
if ($codepoint <= 0xFF) {
// Latin-1 範圍用 %XX
$result .= '%' . strtoupper(dechex($codepoint));
} else {
// Unicode 用 %uXXXX (補齊 4 位)
$result .= '%u' . strtoupper(str_pad(dechex($codepoint), 4, '0', STR_PAD_LEFT));
}
}
return $result;
}
// 測試對比
$test = 'Hello 世界! @#¥%';
echo "JS escape(): " . jsEscape($test) . "\n";
// 輸出: Hello%20%u4E16%u754C%21%20%40%23%C2%A5%25
?>
三、解碼函數 (unescape 相容)
<?php
function jsUnescape(string $string): string
{
// 先處理 %uXXXX
$string = preg_replace_callback('/%u([0-9A-Fa-f]{4})/', function ($m) {
return mb_chr(hexdec($m[1]), 'UTF-8');
}, $string);
// 再處理 %XX
$string = preg_replace_callback('/%([0-9A-Fa-f]{2})/', function ($m) {
return chr(hexdec($m[1]));
}, $string);
return $string;
}
// 往返測試
$orig = '測試 Test 123!@#';
$enc = jsEscape($orig);
$dec = jsUnescape($enc);
var_dump($orig === $dec); // true
?>
四、現代替代方案對比
| 函數 | 標準 | 輸出範例 | PHP 對應 | 推薦度 |
escape() | 已棄用 (ECMA-262 3rd) | %u4E2D | 自定義 jsEscape() | ❌ 僅維舊 |
encodeURI() | 保留 URI 結構字符 | %E4%B8%AD | rawurlencode() + 保留字符 | ⚠️ 完整 URL |
encodeURIComponent() | 編碼所有非保留字符 | %E4%B8%AD | rawurlencode() | ✅ 現代標準 |
rawurlencode() | RFC 3986 | %E4%B8%AD | 原生 | ✅ 推薦 |
五、遷移建議
- 新代碼: 統一用
rawurlencode() (PHP) / encodeURIComponent() (JS),UTF-8 百分號編碼,互通無障礙
- 存量
escape(): 後端用本文 jsEscape() 產生相容字串,前端改用 decodeURIComponent() 解碼 (需先將 %uXXXX 轉為 %XX%XX)
- 雙向轉換工具:
<?php
// escape() → 標準百分號編碼
function escapeToStandard(string $escaped): string
{
// %uXXXX → UTF-8 字節 → %XX%XX...
return preg_replace_callback('/%u([0-9A-Fa-f]{4})/', function ($m) {
$cp = hexdec($m[1]);
$utf8 = mb_chr($cp, 'UTF-8');
return rawurlencode($utf8);
}, $escaped);
}
// 標準 → escape()
function standardToEscape(string $standard): string
{
return jsEscape(rawurldecode($standard));
}
?>