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

Ajax + PHP 快速上手指南:XMLHttpRequest 核心、同源策略、現代 Fetch API 替代

     編輯:PHP 相關知識

Ajax 核心是 XMLHttpRequest (XHR) 物件,實現無刷新數據交互。本文從 XHR 創建、請求流程、PHP 端處理、同源策略、錯誤處理、現代 fetch() 替代方案全維度講解。

XMLHttpRequest 創建與兼容

function createXHR(): XMLHttpRequest {
    if (window.XMLHttpRequest) {
        return new XMLHttpRequest(); // 現代瀏覽器、IE7+
    }
    // IE5/6 兼容(已基本無需)
    try { return new ActiveXObject('Msxml2.XMLHTTP'); } catch (e) {}
    try { return new ActiveXObject('Microsoft.XMLHTTP'); } catch (e) {}
    throw new Error('XMLHttpRequest not supported');
}

完整請求流程

const xhr = createXHR();
const url = 'api.php?action=get_user&id=123';

xhr.open('GET', url, true); // true = 異步

xhr.onreadystatechange = function() {
    if (xhr.readyState === 4) { // 完成
        if (xhr.status >= 200 && xhr.status < 300) {
            const response = xhr.responseText;
            try {
                const data = JSON.parse(response);
                console.log('成功:', data);
                // 更新 DOM
                document.getElementById('result').textContent = data.name;
            } catch (e) {
                console.error('JSON 解析失敗', response);
            }
        } else {
            console.error('HTTP 錯誤:', xhr.status, xhr.statusText);
        }
    }
};

// 可選:超時處理
xhr.timeout = 5000;
xhr.ontimeout = () => console.error('請求超時');

xhr.send(null); // GET 請求體為 null

POST 請求發送數據

const formData = new FormData();
formData.append('username', 'zhangsan');
formData.append('password', 'secret123');

xhr.open('POST', 'login.php', true);
// 發送 FormData 會自動設置 Content-Type: multipart/form-data
xhr.send(formData);

// 或發送 JSON
xhr.open('POST', 'api.php', true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify({action: 'save', data: {...}}));

PHP 端處理最佳實踐

<?php
// api.php
header('Content-Type: application/json; charset=utf-8');

// 統一輸入獲取
$input = json_decode(file_get_contents('php://input'), true) ?? $_POST ?? $_GET;
$action = $input['action'] ?? '';

try {
    switch ($action) {
        case 'get_user':
            $id = (int)($input['id'] ?? 0);
            $user = $db->query("SELECT id, name, email FROM users WHERE id=$id")->fetch(PDO::FETCH_ASSOC);
            echo json_encode(['success' => true, 'data' => $user]);
            break;
        case 'save':
            // 驗證、CSRF 檢查、業務邏輯
            echo json_encode(['success' => true, 'msg' => '保存成功']);
            break;
        default:
            http_response_code(400);
            echo json_encode(['success' => false, 'msg' => '未知 action']);
    }
} catch (Exception $e) {
    http_response_code(500);
    echo json_encode(['success' => false, 'msg' => '服務器錯誤']);
}

同源策略 (SOP) 與 CORS

  • 瀏覽器限制:協議、域名、端口任一不同即跨域;
  • 解決方案:
    1. CORS (Cross-Origin Resource Sharing):服務端響應 Access-Control-Allow-Origin: * 或指定域名;
    2. 代理轉發:Nginx proxy_pass 轉發到目標 API;
    3. JSONP:僅 GET、需服務端配合 callback,已過時。
<?php
// CORS 頭(放在最前)
header('Access-Control-Allow-Origin: https://your-frontend.com');
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, X-Requested-With');
header('Access-Control-Allow-Credentials: true'); // 攜帶 Cookie

// 預檢請求快速返回
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    http_response_code(204);
    exit;
}

現代替代:Fetch API

// GET
fetch('/api.php?action=get_user&id=123')
  .then(r => r.ok ? r.json() : Promise.reject(r.statusText))
  .then(data => console.log(data))
  .catch(err => console.error(err));

// POST JSON
fetch('/api.php', {
  method: 'POST',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({action: 'save', data: {name: 'test'}}),
  credentials: 'include' // 攜帶 Cookie
}).then(r => r.json()).then(console.log);

// 取消請求
const controller = new AbortController();
fetch('/api.php', {signal: controller.signal});
setTimeout(() => controller.abort(), 5000);
  • Promise 風格、鏈式調用、原生支援取消、Streaming、Service Worker;
  • IE 不支援,需 polyfill;
  • 新項目首選 fetch,老項目 XHR 共存。

常見坑點避坑

問題原因修正
readyState 4 但 status 0跨域被攔截 / 網絡斷開檢查 CORS、網絡面板
JSON.parse 報錯PHP 輸出了錯誤信息/空白/警告關閉 display_errors、ob_clean()、統一 json_encode
POST 參數為空Content-Type 不匹配 / php://input 已讀檢查 setRequestHeader、僅讀取一次
攜帶 Cookie 失效跨域需 credentials: true + CORS Allow-Credentials雙端同步設置
重複請求按鈕未禁用、事件冒泡loading 狀態鎖、防抖

封裝通用 Ajax 工具函數

function ajax(options) {
    const {url, method='GET', data, headers={}, timeout=10000, onProgress} = options;
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open(method, url, true);
        xhr.timeout = timeout;
        Object.entries(headers).forEach(([k,v]) => xhr.setRequestHeader(k, v));
        
        if (onProgress) xhr.upload.onprogress = onProgress;
        
        xhr.onload = () => {
            if (xhr.status >= 200 && xhr.status < 300) {
                try { resolve(JSON.parse(xhr.responseText)); }
                catch { resolve(xhr.responseText); }
            } else { reject(new Error(`${xhr.status} ${xhr.statusText}`)); }
        };
        xhr.onerror = () => reject(new Error('Network Error'));
        xhr.ontimeout = () => reject(new Error('Timeout'));
        
        const body = data instanceof FormData ? data : (data ? JSON.stringify(data) : null);
        if (body && !headers['Content-Type']) {
            xhr.setRequestHeader('Content-Type', 'application/json');
        }
        xhr.send(body);
    });
}

// 使用
ajax({url: '/api.php', method: 'POST', data: {action: 'save'}})
  .then(data => console.log(data))
  .catch(err => alert(err.message));

理解 XHR 底層機制能更好地調試網絡問題,現代項目建議直接使用 fetch 或 Axios 等成熟庫。

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