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

PHP 實現 JavaScript escape() 編碼函數完整實作

     編輯:PHP 綜合知識

前言

JavaScript 的 `escape()` 函數雖已棄用(建議用 `encodeURIComponent`),但許多舊系統、API 簽名、第三方整合仍依賴此編碼格式。本文提供純 PHP 實作的 `escape()` 相容函數,支援 Unicode 補充平面、UTF-8 多位元組、大小端序處理。

escape() 編碼規則回顧

字元範圍編碼格式範例
ASCII 字母數字、@ * _ + - . /不編碼`a` `1` `-` `_`
其他 ASCII (32-126)`%XX` (2 位十六進位)空格 `%20`、`#` → `%23`
Unicode BMP (U+0000 ~ U+FFFF)`%uXXXX` (4 位十六進位)`中` → `%u4E2D`
Unicode 補充平面 (U+10000+)`%uXXXX%uXXXX` (Surrogate Pair)`😀` → `%uD83D%uDE00`

完整 PHP 實作

/**
 * PHP 實現 JavaScript escape() 編碼
 * @param string $string UTF-8 編碼字串
 * @return string escape 編碼後字串
 */
function js_escape($string) {
  // 不需編碼的字元集合
  $safeChars = '-_.+@/*0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
  
  $len = strlen($string);
  $output = '';
  $byteIndex = 0;   // 位元組索引
  $charIndex = 0;   // 字元索引(處理多位元組)
  
  while ($byteIndex < $len) {
    $firstByte = ord($string[$byteIndex]);
    $charBytes = 1;
    $codePoint = 0;
    
    // 判斷 UTF-8 字元位元組長度並解碼 code point
    if ($firstByte < 0x80) {                    // 1 byte: 0xxxxxxx (ASCII)
      $codePoint = $firstByte;
      $charBytes = 1;
    } elseif (($firstByte & 0xE0) === 0xC0) {   // 2 bytes: 110xxxxx 10xxxxxx
      $codePoint = (($firstByte & 0x1F) << 6) | (ord($string[$byteIndex+1]) & 0x3F);
      $charBytes = 2;
    } elseif (($firstByte & 0xF0) === 0xE0) {   // 3 bytes: 1110xxxx 10xxxxxx 10xxxxxx
      $codePoint = (($firstByte & 0x0F) << 12) | 
                   ((ord($string[$byteIndex+1]) & 0x3F) << 6) | 
                   (ord($string[$byteIndex+2]) & 0x3F);
      $charBytes = 3;
    } elseif (($firstByte & 0xF8) === 0xF0) {   // 4 bytes: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
      $codePoint = (($firstByte & 0x07) << 18) | 
                   ((ord($string[$byteIndex+1]) & 0x3F) << 12) | 
                   ((ord($string[$byteIndex+2]) & 0x3F) << 6) | 
                   (ord($string[$byteIndex+3]) & 0x3F);
      $charBytes = 4;
    } else {
      // 無效 UTF-8 序列,跳過 1 byte
      $byteIndex++;
      continue;
    }
    
    // 取得原始字元(用於安全字元判斷)
    $rawChar = substr($string, $byteIndex, $charBytes);
    
    // 編碼邏輯
    if ($codePoint < 0x100 && strpos($safeChars, $rawChar) !== false) {
      // ASCII 安全字元:直接輸出
      $output .= $rawChar;
    } elseif ($codePoint < 0x100) {
      // ASCII 非安全字元:%XX
      $output .= '%' . strtoupper(dechex($codePoint));
    } elseif ($codePoint <= 0xFFFF) {
      // BMP 範圍:%uXXXX
      $output .= '%u' . strtoupper(str_pad(dechex($codePoint), 4, '0', STR_PAD_LEFT));
    } else {
      // 補充平面:Surrogate Pair → %uXXXX%uXXXX
      $codePoint -= 0x10000;
      $highSurrogate = 0xD800 | (($codePoint >> 10) & 0x3FF);
      $lowSurrogate = 0xDC00 | ($codePoint & 0x3FF);
      $output .= '%u' . strtoupper(dechex($highSurrogate));
      $output .= '%u' . strtoupper(dechex($lowSurrogate));
    }
    
    $byteIndex += $charBytes;
  }
  
  return $output;
}

/**
 * 反向解碼:unescape()
 */
function js_unescape($string) {
  // 先處理 %uXXXX
  $string = preg_replace_callback('/%u([0-9A-Fa-f]{4})/', function($m) {
    return mb_convert_encoding(hex2bin($m[1]), 'UTF-8', 'UCS-2BE');
  }, $string);
  // 再處理 %XX
  $string = preg_replace_callback('/%([0-9A-Fa-f]{2})/', function($m) {
    return chr(hexdec($m[1]));
  }, $string);
  return $string;
}

測試驗證

$testCases = [
  'Hello World!'           => 'Hello%20World%21',
  '中文測試'               => '%u4E2D%u6587%u6E2C%u8A66',
  '😀🎉 Emoji'             => '%uD83D%uDE00%uD83C%uDF89%20Emoji',
  'email@example.com'    => 'email@example.com',  // 安全字元不編碼
  'path/to/file.php'       => 'path/to/file.php',    // / 是安全字元
  'special chars: #$%^&*'  => 'special%20chars%3A%20%23%24%25%5E%26%2A',
];

foreach ($testCases as $input => $expected) {
  $encoded = js_escape($input);
  $decoded = js_unescape($encoded);
  $status = ($encoded === $expected && $decoded === $input) ? '✅ PASS' : '❌ FAIL';
  echo "$status\nInput:    $input\nEncoded:  $encoded\nExpected: $expected\nDecoded:  $decoded\n\n";
}

效能優化版(適合大量資料)

function js_escape_fast($string) {
  // 使用 mbstring 逐字元處理,效能優於手動位元運算
  $safeChars = '-_.+@/*0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
  $output = '';
  
  $length = mb_strlen($string, 'UTF-8');
  for ($i = 0; $i < $length; $i++) {
    $char = mb_substr($string, $i, 1, 'UTF-8');
    $codePoint = mb_ord($char, 'UTF-8');  // PHP 7.2+
    
    if ($codePoint < 0x100 && strpos($safeChars, $char) !== false) {
      $output .= $char;
    } elseif ($codePoint < 0x100) {
      $output .= '%' . strtoupper(dechex($codePoint));
    } elseif ($codePoint <= 0xFFFF) {
      $output .= '%u' . strtoupper(str_pad(dechex($codePoint), 4, '0', STR_PAD_LEFT));
    } else {
      $codePoint -= 0x10000;
      $output .= '%u' . strtoupper(dechex(0xD800 | (($codePoint >> 10) & 0x3FF)));
      $output .= '%u' . strtoupper(dechex(0xDC00 | ($codePoint & 0x3FF)));
    }
  }
  return $output;
}

應用場景

  1. 舊系統 API 相容:Java、.NET 早期版本常用 escape 簽名
  2. URL 參數雙重編碼:某些閘道器要求參數先 escape 再 urlencode
  3. Cookie 值編碼:避免分號、逗號、空格干擾解析
  4. 前端傳遞至後端解碼:JS `escape()` → PHP `js_unescape()`

總結

JavaScript `escape()` 雖被標記為 Legacy,但在存量系統整合中仍頻繁出現。掌握其編碼規則(特別是 Surrogate Pair 處理),並能用 PHP 精確實現雙向轉換,是後端工程師處理跨語言資料交換的基本功。

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