首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 實現 JavaScript escape/unescape 函數:Unicode 編碼互轉完整方案

     編輯:PHP 相關知識

前後端交互中,JavaScript escape() / unescape() (已棄用但存量代碼大量使用) 編碼的字串,PHP 需正確解碼;反之 PHP 生成給 JS 解碼的字串也需相容。本文提供兩個生產級實現版本,支援中日韓字符、UTF-8/GBK 雙編碼環境,並分析編碼原理與坑點。

編碼原理對比

字符範圍JS escape()PHP urlencode()PHP rawurlencode()
ASCII 字母數字 * @ - _ + . /不編碼不編碼不編碼
空格%20+%20
其他 ASCII (% # & = 等)%XX (16進位)%XX%XX
非 ASCII (中文、日文等)%uXXXX (Unicode 碼位)%XX%XX (UTF-8 字節)%XX%XX (UTF-8 字節)

核心差異:JS escape 非 ASCII 輸出 %uXXXX (UCS-2/UTF-16 碼位);PHP urlencode 輸出 UTF-8 字節的百分號編碼。

版本一:支援 GBK/UTF-8 雙環境的完整類 (推薦)

<?php
/**
 * JS escape/unescape 相容類
 * 內部統一使用 UTF-8,對外支援指定編碼
 */
class JSEncoding
{
    /**
     * 模擬 JavaScript escape()
     * @param string $string 輸入字串
     * @param string $encoding 輸入字串編碼 (utf-8, gbk, gb2312)
     * @return string escape 編碼字串
     */
    public static function escape(string $string, string $encoding = 'utf-8'): string
    {
        // 統一轉為 UTF-8 內部處理
        $utf8 = $encoding === 'utf-8' ? $string : mb_convert_encoding($string, 'UTF-8', $encoding);
        
        $result = '';
        $len = mb_strlen($utf8, 'UTF-8');
        
        for ($i = 0; $i < $len; $i++) {
            $char = mb_substr($utf8, $i, 1, 'UTF-8');
            $codepoint = self::charToCodepoint($char);
            
            if ($codepoint <= 0x7F && !in_array($char, ['*', '@', '-', '_', '+', '.', '/'], true)) {
                // ASCII 安全字符
                $result .= $char;
            } elseif ($codepoint <= 0xFF) {
                // Latin-1 範圍:%XX
                $result .= '%' . strtoupper(dechex($codepoint));
            } else {
                // Unicode 範圍:%uXXXX
                $result .= '%u' . strtoupper(str_pad(dechex($codepoint), 4, '0', STR_PAD_LEFT));
            }
        }
        
        return $result;
    }
    
    /**
     * 模擬 JavaScript unescape()
     * @param string $string escape 編碼字串
     * @param string $outputEncoding 輸出編碼 (utf-8, gbk, gb2312)
     * @return string 解碼後字串
     */
    public static function unescape(string $string, string $outputEncoding = 'utf-8'): string
    {
        // 先處理 %uXXXX (Unicode)
        $utf8 = preg_replace_callback(
            '/%u([0-9A-Fa-f]{4})/',
            function ($matches) {
                $cp = hexdec($matches[1]);
                return self::codepointToUtf8($cp);
            },
            $string
        );
        
        // 再處理 %XX (Latin-1/ASCII)
        $utf8 = preg_replace_callback(
            '/%([0-9A-Fa-f]{2})/',
            function ($matches) {
                return chr(hexdec($matches[1]));
            },
            $utf8
        );
        
        // 輸出編碼轉換
        if ($outputEncoding !== 'utf-8') {
            return mb_convert_encoding($utf8, $outputEncoding, 'UTF-8');
        }
        return $utf8;
    }
    
    private static function charToCodepoint(string $char): int
    {
        // 將單字符轉為 Unicode 碼位
        $bytes = mb_convert_encoding($char, 'UCS-4BE', 'UTF-8');
        return unpack('N', $bytes)[1];
    }
    
    private static function codepointToUtf8(int $cp): string
    {
        if ($cp <= 0x7F) return chr($cp);
        if ($cp <= 0x7FF) return chr(0xC0 | ($cp >> 6)) . chr(0x80 | ($cp & 0x3F));
        if ($cp <= 0xFFFF) return chr(0xE0 | ($cp >> 12)) . chr(0x80 | (($cp >> 6) & 0x3F)) . chr(0x80 | ($cp & 0x3F));
        if ($cp <= 0x10FFFF) return chr(0xF0 | ($cp >> 18)) . chr(0x80 | (($cp >> 12) & 0x3F)) . chr(0x80 | (($cp >> 6) & 0x3F)) . chr(0x80 | ($cp & 0x3F));
        return ''; // 無效碼位
    }
}

// 使用範例
$original = '中文测试 Hello 123';
$escaped = JSEncoding::escape($original, 'utf-8');
echo "Escaped: $escaped\n";
// 中文 => %u4E2D%u6587%u6D4B%u8BD5 Hello 123

$unescaped = JSEncoding::unescape($escaped, 'utf-8');
echo "Unescaped: $unescaped\n";
// 中文测试 Hello 123

// GBK 環境相容
$gbkString = mb_convert_encoding($original, 'GBK', 'UTF-8');
$escapedGbk = JSEncoding::escape($gbkString, 'gbk');
echo "GBK Escaped: $escapedGbk\n";

版本二:輕量函數版 (適用簡單場景)

<?php
/**
 * 輕量版 escape (假設輸入 UTF-8)
 */
function js_escape(string $string): string
{
    $result = '';
    $len = mb_strlen($string, 'UTF-8');
    for ($i = 0; $i < $len; $i++) {
        $char = mb_substr($string, $i, 1, 'UTF-8');
        $cp = unpack('N', mb_convert_encoding($char, 'UCS-4BE', 'UTF-8'))[1];
        if ($cp <= 0x7F && !in_array($char, ['*', '@', '-', '_', '+', '.', '/'], true)) {
            $result .= $char;
        } elseif ($cp <= 0xFF) {
            $result .= '%' . strtoupper(dechex($cp));
        } else {
            $result .= '%u' . strtoupper(str_pad(dechex($cp), 4, '0', STR_PAD_LEFT));
        }
    }
    return $result;
}

/**
 * 輕量版 unescape (輸出 UTF-8)
 */
function js_unescape(string $string): string
{
    // %uXXXX
    $string = preg_replace_callback('/%u([0-9A-Fa-f]{4})/', function($m) {
        $cp = hexdec($m[1]);
        if ($cp <= 0x7F) return chr($cp);
        if ($cp <= 0x7FF) return chr(0xC0 | ($cp >> 6)) . chr(0x80 | ($cp & 0x3F));
        return chr(0xE0 | ($cp >> 12)) . chr(0x80 | (($cp >> 6) & 0x3F)) . chr(0x80 | ($cp & 0x3F));
    }, $string);
    
    // %XX
    $string = preg_replace_callback('/%([0-9A-Fa-f]{2})/', function($m) {
        return chr(hexdec($m[1]));
    }, $string);
    
    return $string;
}

// 測試
$str = '測試 Test 日本語';
$esc = js_escape($str);
echo $esc . "\n"; // %u6E2C%u8BD5 Test %u65E5%u672C%u8A9E
echo js_unescape($esc) . "\n"; // 測試 Test 日本語

常見坑點與解決

問題原因解決
中文解碼成亂碼編碼不匹配 (JS escape 用 UCS-2,PHP 預期 UTF-8)統一指定編碼參數,或雙端約定 UTF-8
%uXXXX 解碼失敗正則未匹配大小寫、碼位超出 BMP (Emoji > 0xFFFF)擴展正則支援 %U、補充平面 (需 6 字節 UTF-8)
空格變成 +PHP urldecode 將 + 視為空格用 rawurldecode 或自行實現
特殊字符 (* @ - _ + . /) 被編碼JS escape 不編碼這些,PHP urlencode 會編碼白名單過濾或自行實現

現代替代方案建議

  • 雙端統一使用 encodeURIComponent / decodeURIComponent:輸出 UTF-8 字節編碼,PHP 用 rawurldecode / rawurlencode 完美互通
  • JSON 傳輸:JSON.stringify(obj) → PHP json_decode($raw, true),完全避開編碼問題
  • Base64:二進制/特殊字串傳輸,btoa() / base64_decode()

遷移策略

  1. 新介面強制 encodeURIComponent + JSON
  2. 舊介面引入 JSEncoding 類兼容
  3. 逐步重構舊端點,移除 escape/unescape 依賴
  4. 前端 ESLint 規則禁用 escape/unescape

JS escape/unescape 雖被標準棄用,但存量系統廣泛存在。掌握其編碼邏輯 (UCS-2 碼位輸出 %uXXXX) 與 PHP 正確互轉,是維護舊系統、對接第三方介面的必備技能。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved