前言:軟件開發流程
AJAX:前端與后端的數據交互
前后端協作基礎
Web應用的核心是“數據交互”,前端負責展示與交互,后端負責處理邏輯與數據存儲,二者通過網絡請求協作。
(1)項目開發流程與崗位分工
- 核心流程:產品經理(需求)→ UI設計師(界面)→ 前端(可視化)→ 后端(邏輯)→ 測試(驗證)→ 運維(上線)。
- 核心崗位:
- 前端:負責用戶可見的界面(HTML/CSS/JS/Vue/React/UI框架例如element-ui/Ant-Design等)、數據展示、用戶交互。
- 后端:處理業務邏輯(如登錄驗證、訂單處理)、操作數據庫、提供數據接口。
- 數據庫:存儲結構化數據(如用戶信息、商品庫存),僅允許后端訪問。
(2)前后端職責邊界
前端職責 | 后端職責 |
---|---|
構建用戶界面(靜態+動態渲染) | 處理業務邏輯(如權限校驗、計算) |
收集用戶操作(如表單輸入、點擊) | 操作數據庫(增刪改查數據) |
發送請求到后端,接收并展示數據 | 提供API接口,返回格式化數據(JSON) |
處理前端驗證(如表單格式) | 處理后端驗證(如數據合法性、權限) |
數據流轉全流程
用戶操作觸發的數據交互需經過“前端→后端→數據庫→后端→前端”的完整鏈路:
- 用戶操作:如點擊“查詢商品”按鈕。
- 前端處理:收集參數(如商品ID),通過AJAX發送請求到后端接口(如
/api/goods
)。 - 后端處理:
- 接收請求,驗證參數(如用戶權限)。
- 向后端數據庫發送查詢命令(如SQL語句)。
- 數據庫響應:返回符合條件的數據(如商品詳情)。
- 后端響應:將數據庫數據格式化(如JSON),返回給前端。
- 前端渲染:接收數據,通過DOM操作更新頁面(如展示商品信息)。
一、服務器與云端基礎
服務器:數據存儲與處理的核心
服務器是支撐Web應用運行的硬件基礎,其本質是一臺高性能計算機,負責接收前端請求、處理業務邏輯并返回數據。
(1)服務器類型與特點
類型 | 定義與適用場景 | 優勢 | 劣勢 |
---|---|---|---|
物理服務器 | 獨立的實體計算機,硬件資源獨占(如企業自建機房服務器) | 性能穩定、安全性高、可完全定制 | 成本高(幾萬元起)、需專人維護、擴容困難 |
云服務器 | 基于云計算的虛擬服務器(如阿里云ECS、騰訊云CVM),資源按需分配 | 成本低(入門級100元/月起)、彈性擴容、無需維護硬件 | 性能受限于服務商配置、依賴網絡穩定性 |
虛擬主機 | 一臺物理服務器分割出的多個虛擬空間(共享硬件資源) | 價格極低(幾十元/年)、操作簡單 | 資源受限(帶寬、存儲)、自主性差 |
(2)云服務器核心特性
- 彈性計算:可根據訪問量實時調整CPU、內存、帶寬(如促銷活動時臨時擴容)。
- 按需付費:支持按小時、按月付費,避免資源浪費(適合個人開發者和初創項目)。
- 高可用性:云服務商提供多地域部署、容災備份,降低服務器宕機風險(如阿里云的多可用區)。
(3)主流云服務商對比
服務商 | 優勢領域 | 適合場景 |
---|---|---|
阿里云 | 國內市場份額最高、生態完善(ECS+OSS+CDN)、支持中小到大型項目 | 國內業務、需要穩定備案服務的項目 |
騰訊云 | 社交場景優化好(如小程序對接)、價格親民、新手友好 | 小程序/公眾號關聯項目、初創團隊 |
AWS(亞馬遜云) | 全球節點最多、技術成熟、支持復雜架構(如跨境業務) | 海外業務、大型企業級項目 |
華為云 | 政務項目優勢明顯、安全合規性強 | 政企合作項目、對安全性要求高的場景 |
域名:服務器的“門牌號”
域名是服務器的人類可讀標識(如baidu.com
),用于替代難記的IP地址(如180.101.50.242
),是用戶訪問網站的入口。
(1)域名結構與類型
- 結構:由“主機名.二級域名.頂級域名”組成(如
blog.baidu.com
中,blog
是主機名,baidu
是二級域名,com
是頂級域名)。 - 類型:
- 頂級域名(TLD):如
.com
(商業)、.cn
(中國)、.org
(非營利)、.xyz
(通用)。 - 二級域名:如
baidu.com
(需注冊),三級域名:如map.baidu.com
(由二級域名衍生,無需額外注冊)。
- 頂級域名(TLD):如
(2)域名注冊與定價
- 注冊流程:通過域名服務商(如阿里云萬網、騰訊云域名)查詢→購買→實名認證→解析(綁定IP)。
- 定價因素:
- 頂級域名后綴:
.com
(約60元/年)、.cn
(約30元/年)較貴,.xyz
(約10元/年)等小眾后綴便宜。 - 域名長度與含義:短域名(如
jd.com
)、有特殊含義的域名(如chat.com
)價格極高(萬元至千萬元級)。
- 頂級域名后綴:
- 搶注風險:域名過期后會進入贖回期(通常30天),未續費則被公開搶注,需及時續費。
(3)域名解析與備案
- 解析:將域名指向服務器IP的過程,通過DNS服務器完成。常用解析記錄:
- A記錄:直接指向IP地址(如
127.0.0.1
)。 - CNAME記錄:指向另一個域名(如將
www.abc.com
指向abc.com
)。
- A記錄:直接指向IP地址(如
- 備案:國內服務器使用域名必須備案(免費,約1-2周),否則無法通過域名訪問;海外服務器可免備案,但訪問速度較慢。
服務器空間:項目的“存儲單元”
服務器空間是服務器中劃分出的專用存儲區域,用于存放網站文件(HTML、CSS、JS、圖片等),類比“公寓中的單間”。
(1)空間類型對比
類型 | 定義 | 適用場景 |
---|---|---|
虛擬主機 | 多人共享一臺服務器的硬件資源(CPU、內存、帶寬),僅提供基礎存儲和運行環境 | 個人博客、小型靜態網站(日訪問量<1000) |
VPS(虛擬專用服務器) | 虛擬出獨立的服務器環境,資源隔離,可自主配置(如安裝軟件、修改端口) | 中小型動態網站(日訪問量1000-10000) |
獨立服務器 | 完全獨占一臺服務器的所有資源 | 大型網站、高并發業務(日訪問量>10萬) |
(2)空間核心參數
- 存儲容量:影響可存放文件的大小(如1GB空間可存放約500張高清圖片)。
- 帶寬:決定數據傳輸速度,帶寬越低,多人同時訪問時越容易卡頓(推薦個人項目至少1M帶寬)。
- 操作系統:Windows(支持ASP、.NET)或Linux(支持PHP、Python,更穩定、開源)。
云端服務:靈活高效的資源方案
“云端”指通過互聯網訪問的遠程服務器資源,無需自建機房,按需使用,是現代Web開發的主流選擇。
(1)主流云服務類型
- 云服務器(ECS/EC2):彈性計算服務,提供虛擬服務器實例,可完全定制(適合中大型項目)。
- 輕量應用服務器:簡化版云服務器,預裝常用環境(如LAMP、Node.js),操作簡單(適合新手)。
- 對象存儲(OSS/S3):專門存儲靜態資源(圖片、視頻、文檔),支持CDN加速(提升訪問速度)。
- 數據庫服務(RDS):托管的數據庫(MySQL、MongoDB等),自動備份、高可用(無需手動維護)。
(2)云端服務優勢
- 低成本:無需購買硬件,按使用量付費(如1核2G云服務器約500元/年)。
- 高可靠:服務商提供99.9%以上的運行保障,多地域備份避免數據丟失。
- 易擴展:業務增長時可快速提升配置(如從1核2G升級到4核8G),無需停機。
二、AJAX:前端與后端的異步交互
AJAX(Asynchronous JavaScript and XML)是一種在不刷新頁面的情況下,通過JavaScript與后端進行數據交互的技術,核心是“異步請求+局部更新”。
AJAX的核心特性:異步執行
- 異步含義:請求發送后,瀏覽器不會等待響應,而是繼續執行后續代碼(避免頁面卡頓)。
- 執行順序:同步代碼(如
console.log
)優先于異步代碼(AJAX回調)執行,即使AJAX寫在前面。
示例:
console.log("1. 開始發送請求");// 創建AJAX請求(異步)
const xhr = new XMLHttpRequest();
xhr.open("GET", "/api/data", true);
xhr.onreadystatechange = function() {if (xhr.readyState === 4 && xhr.status === 200) {console.log("3. 請求成功,收到數據"); // 最后執行}
};
xhr.send();console.log("2. 請求已發送,繼續執行"); // 先于回調執行
輸出順序:1 → 2 → 3(體現異步特性)
AJAX的完整使用步驟
(1)創建核心對象(XMLHttpRequest)
XMLHttpRequest
是瀏覽器內置的AJAX核心對象,負責發送請求和接收響應:
// 兼容IE6及以下(現代瀏覽器直接用new XMLHttpRequest())
const xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
(2)創建請求(open方法)
xhr.open(method, url, async)
用于初始化請求,參數說明:
method
:請求方式(GET
/POST
/PUT
/DELETE
,大小寫不敏感)。url
:請求地址(服務器接口URL,如"http://localhost:8080/api/user"
)。async
:是否異步(必須為true
,否則失去AJAX意義)。
xhr.open("GET", "/api/user?id=1", true); // GET請求示例
// 或
xhr.open("POST", "/api/login", true); // POST請求示例
(3)設置請求頭(POST專用)
POST請求需手動設置Content-Type
請求頭,否則后端無法正確解析參數:
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
(4)發送請求(send方法)
xhr.send(data)
用于發送請求,data
為請求參數(僅POST需要):
- GET請求:參數拼接在URL后,
send
傳null
。 - POST請求:參數以
key=value&key2=value2
格式傳入send
。
// GET請求(無參數)
xhr.send(null);// POST請求(帶參數)
xhr.send("username=admin&password=123456");
(5)監聽響應(onreadystatechange事件)
當請求狀態(readyState
)變化時觸發,需同時滿足“請求完成(readyState=4
)”和“請求成功(status=200
)”才處理響應:
readyState 值 | 狀態描述 |
---|---|
0 | 請求未初始化(未調用open) |
1 | 正在發送請求(已調用open,未調用send) |
2 | 請求發送完成(已調用send) |
3 | 正在接收響應(部分數據已返回) |
4 | 響應接收完成(可處理數據) |
示例:
xhr.onreadystatechange = function() {// 僅當請求完成且成功時處理if (xhr.readyState === 4) {if (xhr.status === 200) {const data = xhr.responseText; // 獲取響應數據(文本格式)console.log("響應數據:", data);} else {console.error("請求失敗,狀態碼:", xhr.status);}}
};
(6)解析響應數據
xhr.responseText
返回純文本數據,需根據后端格式解析(通常為JSON):
if (xhr.status === 200) {const data = JSON.parse(xhr.responseText); // 轉換為JSON對象console.log("用戶姓名:", data.name);
}
3. GET與POST請求的核心區別
對比維度 | GET請求 | POST請求 |
---|---|---|
參數位置 | 拼接在URL后(url?key=value&key2=value2 ) | 放在請求體中(send 方法傳入) |
可見性 | 地址欄可見(不安全) | 不可見(相對安全) |
參數長度限制 | 有限制(URL長度通常≤2048字符) | 無限制(理論上) |
緩存 | 可被瀏覽器緩存(如刷新后復用結果) | 默認不緩存 |
用途 | 用于查詢數據(如搜索、獲取列表) | 用于提交數據(如登錄、注冊、上傳) |
請求頭 | 無需額外設置 | 必須設置Content-Type: application/x-www-form-urlencoded |
4. 常用HTTP狀態碼解析
狀態碼是服務器返回的請求結果標識,掌握常見狀態碼可快速定位問題:
狀態碼 | 含義 | 常見場景與排查方向 |
---|---|---|
200 | 請求成功 | 后端正常處理并返回數據 |
201 | 創建成功 | 新增資源(如注冊用戶、發布文章)成功 |
400 | 請求錯誤 | 參數格式錯誤(如缺少必填項、格式不匹配) |
401 | 未授權 | 未登錄或登錄過期(需重新登錄) |
403 | 禁止訪問 | 無權限訪問(如普通用戶訪問管理員接口) |
404 | 資源未找到 | URL錯誤或資源已刪除(檢查請求地址) |
500 | 服務器內部錯誤 | 后端代碼報錯(需后端排查日志) |
503 | 服務不可用 | 服務器過載或維護中(稍后重試) |
5. AJAX的現代替代方案
傳統XMLHttpRequest
語法繁瑣,現代開發中常用更簡潔的方案:
(1)fetch API(瀏覽器內置,基于Promise)
fetch("/api/user?id=1").then(response => {if (!response.ok) throw new Error("請求失敗");return response.json(); // 自動解析JSON}).then(data => console.log("用戶數據:", data)).catch(error => console.error("錯誤:", error));
(2)axios(第三方庫,推薦)
封裝了XMLHttpRequest
,支持Promise、攔截器、請求取消等:
// 安裝:npm install axios
import axios from "axios";axios.get("/api/user?id=1").then(response => console.log("用戶數據:", response.data)).catch(error => console.error("錯誤:", error));// POST請求
axios.post("/api/login", { username: "admin", password: "123" }).then(response => console.log("登錄結果:", response.data));
三、本地服務器搭建與項目部署
1. 本地服務器:開發與測試環境
本地服務器是在個人電腦上搭建的模擬服務器環境,用于開發時調試AJAX請求和項目運行效果。可以用于個人學習理解項目開發到上線部署全流程。
(1)常用工具
- XAMPP:跨平臺(Windows/macOS/Linux),集成Apache(Web服務器)、MySQL(數據庫)、PHP(后端語言),適合新手。
- WAMP:僅支持Windows,功能與XAMPP類似。
- MAMP:僅支持macOS,對蘋果設備兼容性更好。
(2)XAMPP搭建步驟
- 下載安裝:從XAMPP官網下載對應系統版本,按提示安裝(默認路徑即可)。
- 啟動服務:打開XAMPP控制面板,點擊“Start”啟動Apache(Web服務器)。
- 驗證運行:瀏覽器訪問
http://localhost
或http://127.0.0.1
,出現XAMPP默認頁面即成功。
(3)端口號配置
端口號是服務器上區分不同服務的“房間號”,默認端口:
- HTTP協議:80(可省略,如
localhost:80
等價于localhost
)。 - HTTPS協議:443。
- MySQL數據庫:3306。
端口沖突解決:若80端口被占用(如被IIS、迅雷占用),可修改Apache端口:
- XAMPP控制面板點擊“Config”→“Apache (httpd.conf)”。
- 搜索
Listen 80
,改為Listen 8080
(或其他未占用端口)。 - 重啟Apache,訪問時需帶端口:
http://localhost:8080
。
2. 項目部署:從本地到線上
部署是將開發完成的項目文件上傳到服務器,使互聯網用戶可訪問的過程。
(1)本地部署步驟(XAMPP為例)
- 放置文件:將項目文件(HTML、CSS、JS等)復制到XAMPP安裝目錄的
htdocs
文件夾(如C:\xampp\htdocs\myproject
)。
- 訪問項目:瀏覽器輸入
http://localhost/myproject/index.html
(或http://127.0.0.1:8080/myproject/index.html
,帶端口時)。
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>AJAX</title>
</head>
<body>
<p>一個上線的項目</p><script>console.log('一個上線的項目');</script>
</body>
</html>
(2)線上部署步驟(云服務器為例)
- 購買云服務器:選擇合適配置(如1核2G,1M帶寬),操作系統選Linux(如CentOS)。
- 安裝環境:通過服務器管理面板(如寶塔面板)安裝Apache/Nginx、PHP(如需)。
- 上傳文件:用FTP工具(如FileZilla)將項目文件上傳到服務器的網站根目錄(如
/www/wwwroot/域名
)。 - 配置域名:在云服務商控制臺將域名解析到服務器IP,完成備案(國內服務器)。
- 測試訪問:通過域名訪問項目,檢查是否正常運行(如
http://www.你的域名.com
)。
(3)部署注意事項
- 路徑問題:項目中資源引用需用相對路徑(如
./css/style.css
),避免絕對路徑(如C:\...
)。 - 權限設置:服務器文件需設置正確權限(如Linux中
chmod 755
),避免因權限不足導致訪問失敗。 - 備份:定期備份項目文件和數據庫,防止數據丟失。
- HTTPS:通過云服務商申請免費SSL證書(如Let’s Encrypt),開啟HTTPS(提升安全性,瀏覽器地址欄顯示鎖圖標)。
(4)常見問題與解決
- 端口沖突(啟動失敗,日志顯示“Port 80 in use”):
- 原因:80端口被其他程序(如IIS、迅雷、Skype)占用;
- 解決:
- 關閉占用端口的程序(通過“任務管理器”結束進程);
- 修改Apache端口:XAMPP控制面板→“Config”→“Apache (httpd.conf)”,搜索
Listen 80
改為Listen 8080
,重啟Apache后通過http://localhost:8080
訪問。
- 權限問題(無法訪問項目文件):
- 確保項目文件放在XAMPP的
htdocs
目錄下(如C:\xampp\htdocs\myproject
); - 首次打開
htdocs
時,在VS Code中點擊“信任父文件夾”授權編輯。
- 確保項目文件放在XAMPP的
- 開發注意
- 必須通過服務器IP訪問(而非雙擊本地文件),否則AJAX請求會因“跨域”或“協議限制”失敗;
- 修改文件后需刷新瀏覽器,確保操作的是
htdocs
目錄下的文件(避免本地副本與服務器文件不一致)。
四、AJAX請求完整流程(結合PHP后端)
AJAX的核心是前端與后端的異步數據交互,以下以“前端發送請求→PHP處理→前端接收響應”為例,詳解完整流程。
1. 后端準備:創建PHP接口
PHP是常用的后端語言,可快速處理前端請求并返回數據(XAMPP默認集成PHP環境)。
(1)PHP文件創建與基礎語法
- 文件規范:
- 擴展名必須為
.php
(如api.php
); - 保存路徑:
htdocs
目錄下(如C:\xampp\htdocs\api.php
)。
- 擴展名必須為
- 基礎語法:
<?php
// 簡單響應示例
echo "Hello, AJAX!"; // 輸出字符串,前端通過responseText接收
?>
- PHP代碼必須包裹在 `<?php ?>` 標簽內;
- 輸出數據用 `echo` 命令,語句以分號 `;` 結尾(嚴格要求,缺少會報錯)。
(2)PHP接收前端參數
后端通過 $_REQUEST
全局變量接收前端傳遞的參數(支持GET和POST方式),參數名需與前端完全一致。
示例:接收前端的 username
和 hobby
參數并返回對應響應
<?php
// 接收參數(前后端參數名必須一致)
$username = $_REQUEST["username"]; // 獲取username參數
$hobby = $_REQUEST["hobby"]; // 獲取hobby參數// 根據參數返回不同響應
if ($hobby === "打籃球") {echo $username . " 喜歡打籃球!";
} else if ($hobby === "踢足球") {echo $username . " 喜歡踢足球!";
} else {echo $username . " 沒有明確的愛好~";
}
?>
2. 前端實現:發送AJAX請求
前端通過 XMLHttpRequest
對象發送請求,需嚴格遵循“創建對象→配置請求→發送→處理響應”步驟。
(1)GET請求實現(帶參數)
GET請求參數拼接在URL后,格式為 ?key1=value1&key2=value2
。
// 1. 創建XMLHttpRequest核心對象
const xhr = new XMLHttpRequest();// 2. 配置請求(GET方式,帶參數)
// URL格式:接口地址?參數1=值1&參數2=值2
xhr.open("GET", "api.php?username=小明&hobby=打籃球", true);// 3. 發送請求(GET請求send傳null)
xhr.send(null);// 4. 監聽響應狀態變化
xhr.onreadystatechange = function() {// 僅當請求完成(readyState=4)且成功(status=200)時處理if (xhr.readyState === 4 && xhr.status === 200) {const response = xhr.responseText; // 獲取后端返回的文本console.log("響應結果:", response); // 輸出:"小明 喜歡打籃球!"document.getElementById("result").innerHTML = response; // 渲染到頁面}
};
(2)POST請求實現(帶參數)
POST請求參數放在 send
方法中,需額外設置 Content-Type
請求頭,否則后端無法解析。
// 1. 創建核心對象
const xhr = new XMLHttpRequest();// 2. 配置請求(POST方式)
xhr.open("POST", "api.php", true);// 3. 設置請求頭(POST必須,指定參數格式)
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");// 4. 發送請求(參數放在send中,格式同GET)
xhr.send("username=小紅&hobby=踢足球");// 5. 處理響應
xhr.onreadystatechange = function() {if (xhr.readyState === 4 && xhr.status === 200) {document.getElementById("result").innerHTML = xhr.responseText; // 輸出:"小紅 喜歡踢足球!"}
};
(3)請求狀態監控詳解
readyState
:表示請求生命周期狀態(0-4),僅當值為4時表示響應完全接收。status
:HTTP狀態碼,200表示成功,404表示接口不存在,500表示后端代碼錯誤。
狀態碼處理最佳實踐:
xhr.onreadystatechange = function() {if (xhr.readyState === 4) {if (xhr.status === 200) {// 成功處理console.log("請求成功:", xhr.responseText);} else if (xhr.status === 404) {console.error("接口不存在,請檢查URL");} else if (xhr.status === 500) {console.error("后端出錯,請聯系開發人員");} else {console.error("請求失敗,狀態碼:", xhr.status);}}
};
開發實踐建議
- 敏感數據(如密碼、銀行卡號)必須用POST請求,且配合HTTPS加密傳輸;
- 頻繁查詢(如商品列表)用GET請求,利用瀏覽器緩存提升性能;
- 文件上傳只能用POST請求(
Content-Type: multipart/form-data
); - 后端接口會明確指定請求方式(如API文檔標注
GET /api/user
或POST /api/login
),前端需嚴格遵循。
五、JSON:前后端數據交換的標準格式
JSON(JavaScript Object Notation)是輕量級數據交換格式,因簡潔、跨語言、易解析的特性,成為前后端交互的行業標準。
1. JSON基本語法規則
- 數據結構:
- 對象:用
{}
包裹的鍵值對集合,鍵必須用雙引號"
包裹(如{"name": "小明"}
); - 數組:用
[]
包裹的有序值集合(如["籃球", "足球"]
)。
- 對象:用
- 值類型:字符串(雙引號)、數字、布爾值(
true
/false
)、數組、對象、null
。 - 語法要求:
- 鍵和字符串值必須用雙引號(單引號無效);
- 末尾不能有多余逗號(如
{"a": 1,}
錯誤); - 不支持注釋(JSON5擴展支持,但不推薦)。
正確示例:
{"name": "小明","age": 18,"hobbies": ["打籃球", "聽音樂"],"isStudent": true,"address": null
}
2. JSON與JavaScript對象的轉換
前端需將后端返回的JSON字符串轉為JS對象才能使用,反之,發送復雜數據時需將JS對象轉為JSON字符串。
(1)JSON字符串 → JS對象:JSON.parse()
// 后端返回的JSON字符串
const jsonStr = '{"name": "小明", "age": 18}';// 轉換為JS對象
const user = JSON.parse(jsonStr);
console.log(user.name); // "小明"(可直接訪問屬性)
- 高級用法:通過
reviver
參數過濾或轉換數據
const user = JSON.parse(jsonStr, (key, value) => {if (key === "age") return value + 1; // 年齡+1return value;
});
console.log(user.age); // 19
(2)JS對象 → JSON字符串:JSON.stringify()
// JS對象
const user = { name: "小紅", age: 17, hobbies: ["踢足球"] };// 轉換為JSON字符串
const jsonStr = JSON.stringify(user);
console.log(jsonStr); // '{"name":"小紅","age":17,"hobbies":["踢足球"]}'
- 高級用法:
// 只轉換name和hobbies,且格式化輸出
const jsonStr = JSON.stringify(user, ["name", "hobbies"], 2);
/* 輸出:
{"name": "小紅","hobbies": ["踢足球"]
}
*/
- `replacer`:過濾需要轉換的屬性;
- `space`:格式化輸出(便于閱讀)。
3. 常見JSON解析錯誤及解決
- 錯誤1:屬性名未用雙引號
{ name: "小明" } // 錯誤,鍵必須用雙引號
解決:改為 {"name": "小明"}
。
- 錯誤2:使用單引號
{'name': '小明'} // 錯誤,雙引號是唯一標準
解決:統一替換為雙引號。
- 錯誤3:尾部多余逗號
{"name": "小明", "age": 18,} // 錯誤,末尾不能有逗號
解決:刪除多余逗號。
- 錯誤4:包含函數/Symbol
const obj = { fn: () => {}, s: Symbol("id") };
JSON.stringify(obj); // 結果為"{}"(函數和Symbol會被忽略)
解決:避免在JSON中包含這些類型,或提前處理。
五、AJAX實戰:動態數據交互與深復制
1. 動態數據渲染流程
結合JSON實現“前端請求→后端返回JSON→前端解析并渲染”的完整流程:
后端PHP(user_api.php
):
<?php
// 模擬從數據庫獲取數據
$user = ["name" => $_REQUEST["name"],"age" => (int)$_REQUEST["age"], // 轉換為數字類型"hobbies" => explode(",", $_REQUEST["hobbies"]) // 字符串轉數組
];// 輸出JSON格式字符串(必須用雙引號)
echo json_encode($user); // PHP內置函數,自動轉換為JSON
?>
前端JS:
// 獲取用戶輸入
const name = document.getElementById("name").value;
const age = document.getElementById("age").value;
const hobbies = document.getElementById("hobbies").value;// 發送POST請求
const xhr = new XMLHttpRequest();
xhr.open("POST", "user_api.php", true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send(`name=${name}&age=${age}&hobbies=${hobbies}`);// 處理響應
xhr.onreadystatechange = function() {if (xhr.readyState === 4 && xhr.status === 200) {// 解析JSON字符串為JS對象const user = JSON.parse(xhr.responseText);// 動態渲染到頁面const html = `<h3>${user.name}</h3><p>年齡:${user.age}</p><p>愛好:${user.hobbies.join("、")}</p>`;document.getElementById("userInfo").innerHTML = html;}
};
2. 基于JSON的對象深復制
深復制(完全復制對象,修改副本不影響原對象)可通過“JSON.stringify + JSON.parse”實現,適合簡單對象。
// 原對象
const obj = { name: "小明", info: { age: 18, hobby: "籃球" } };// 深復制
const objCopy = JSON.parse(JSON.stringify(obj));// 修改副本,原對象不受影響
objCopy.info.age = 19;
console.log(obj.info.age); // 18(原對象未變)
console.log(objCopy.info.age); // 19(副本修改成功)
局限性:
- 無法復制函數、
Symbol
、Date
(會轉為字符串)、RegExp
等特殊類型; - 存在循環引用時會報錯(如
obj.self = obj
)。
替代方案:遞歸深復制(處理特殊類型)
function deepClone(obj) {if (obj === null || typeof obj !== "object") return obj;let clone = Array.isArray(obj) ? [] : {};for (let key in obj) {if (obj.hasOwnProperty(key)) {clone[key] = deepClone(obj[key]); // 遞歸復制}}return clone;
}
六、AJAX實戰:省市區聯動案例-動態數據交互實踐
省市區聯動是AJAX的經典應用場景,核心是通過用戶選擇觸發異步請求,動態加載對應級別的數據(如選擇省份后加載對應城市,選擇城市后加載對應區縣),避免前端硬編碼數據,適應數據動態變化需求。
1. 案例核心需求與技術方案
- 需求:實現三級聯動下拉框(省份→城市→區縣),數據從后端動態獲取,支持數據頻繁更新(如新增城市、調整行政區劃)。
- 技術棧:HTML(select/option)+ JavaScript(AJAX)+ 后端(如PHP)+ 數據接口(省份/城市/區縣接口)。
2. 前端實現步驟
(1)HTML結構:創建聯動下拉框
使用select
元素創建三級下拉框,初始選項為“請選擇”,后續通過JS動態填充數據:
<!-- 省市區聯動容器 -->
<div class="location-select"><select id="province"><option value="">請選擇省份</option></select><select id="city"><option value="">請選擇城市</option></select><select id="district"><option value="">請選擇區縣</option></select>
</div>
(2)JS邏輯:通過AJAX動態加載數據
核心思路:監聽下拉框onchange
事件,根據選中值發送AJAX請求,獲取對應數據后渲染到下一級下拉框。
① 加載省份數據(頁面初始化時)
// 頁面加載完成后自動加載省份數據
window.onload = function() {loadProvinces();
};// 加載省份數據
function loadProvinces() {const xhr = new XMLHttpRequest();// 請求省份接口(無參數,返回所有省份)xhr.open("GET", "province.php", true);xhr.onreadystatechange = function() {if (xhr.readyState === 4 && xhr.status === 200) {const provinces = xhr.responseText.split(","); // 后端返回"北京,上海,廣東..."const provinceSelect = document.getElementById("province");// 動態創建option并添加到省份下拉框provinces.forEach(province => {const option = document.createElement("option");option.value = province; // 選項值(如"廣東")option.textContent = province; // 顯示文本provinceSelect.appendChild(option);});}};xhr.send(null);
}
② 加載城市數據(省份選擇變化時)
// 監聽省份下拉框變化
document.getElementById("province").addEventListener("change", function() {const province = this.value; // 獲取選中的省份(如"廣東")if (!province) {// 若未選擇省份,清空城市和區縣document.getElementById("city").innerHTML = '<option value="">請選擇城市</option>';document.getElementById("district").innerHTML = '<option value="">請選擇區縣</option>';return;}loadCities(province); // 加載對應省份的城市
});// 加載城市數據
function loadCities(province) {const xhr = new XMLHttpRequest();xhr.open("POST", "city.php", true);// 設置POST請求頭xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");xhr.onreadystatechange = function() {if (xhr.readyState === 4 && xhr.status === 200) {const cities = xhr.responseText.split(","); // 后端返回"廣州,深圳,珠海..."const citySelect = document.getElementById("city");// 清空原有城市選項(保留默認項)citySelect.innerHTML = '<option value="">請選擇城市</option>';// 動態添加城市選項cities.forEach(city => {const option = document.createElement("option");option.value = city;option.textContent = city;citySelect.appendChild(option);});// 清空區縣(城市變化后區縣需重新加載)document.getElementById("district").innerHTML = '<option value="">請選擇區縣</option>';}};// 發送省份參數xhr.send(`province=${province}`);
}
③ 加載區縣數據(城市選擇變化時)
類似城市加載邏輯,監聽城市下拉框change
事件,請求區縣接口并渲染:
// 監聽城市下拉框變化
document.getElementById("city").addEventListener("change", function() {const city = this.value;if (!city) {document.getElementById("district").innerHTML = '<option value="">請選擇區縣</option>';return;}loadDistricts(city); // 加載對應城市的區縣
});// 加載區縣數據(實現類似loadCities)
function loadDistricts(city) {// 省略AJAX請求代碼,邏輯同loadCities// 后端接口district.php接收city參數,返回"天河區,越秀區,海珠區..."
}
3. 后端接口實現(PHP示例)
(1)省份接口(province.php
)
返回所有省份數據(字符串格式,用逗號分隔):
<?php
// 模擬從數據庫獲取省份數據
$provinces = ["北京", "上海", "廣東", "江蘇", "浙江"];
// 轉為逗號分隔的字符串返回(實際開發推薦JSON)
echo implode(",", $provinces);
?>
(2)城市接口(city.php
)
根據省份參數返回對應城市:
<?php
// 接收省份參數
$province = $_REQUEST["province"];// 模擬數據庫數據(省份→城市映射)
$cityMap = ["廣東" => ["廣州", "深圳", "珠海", "佛山"],"江蘇" => ["南京", "蘇州", "無錫", "常州"],"北京" => ["北京市"] // 直轄市無地級市,直接返回自身
];// 返回對應城市(默認空數組)
$cities = $cityMap[$province] ?? [];
echo implode(",", $cities);
?>
4. 優化技巧與注意事項
(1)性能優化
- 清空選項:使用
innerHTML
直接替換比循環刪除option
更高效(如citySelect.innerHTML = '<option value="">請選擇城市</option>'
)。 - 代碼復用:封裝通用AJAX函數,減少重復代碼:
// 通用AJAX請求函數
function ajax(url, method, data, successCallback) {const xhr = new XMLHttpRequest();xhr.open(method, url, true);if (method === "POST") {xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");}xhr.onreadystatechange = function() {if (xhr.readyState === 4 && xhr.status === 200) {successCallback(xhr.responseText); // 成功回調處理數據}};xhr.send(data);
}// 調用示例(加載省份)
ajax("province.php", "GET", null, function(response) {// 處理省份數據...
});
(2)用戶體驗優化
- 加載狀態提示:請求過程中顯示“加載中…”,避免用戶誤以為無響應:
function loadCities(province) {const citySelect = document.getElementById("city");citySelect.innerHTML = '<option value="">加載中...</option>'; // 顯示加載狀態// 后續AJAX請求...
}
- 數據校驗:過濾無效數據(如后端返回空值時顯示“暫無數據”)。
(3)數據格式升級
文檔中使用“逗號分隔字符串”傳遞數據,實際開發推薦JSON格式(更規范,支持復雜結構):
- 后端(PHP)返回JSON:
// province.php(返回JSON)
$provinces = ["北京", "上海", "廣東"];
header("Content-Type: application/json"); // 聲明JSON類型
echo json_encode($provinces); // 返回["北京","上海","廣東"]
- 前端解析JSON:
// 加載省份(JSON格式處理)
ajax("province.php", "GET", null, function(response) {const provinces = JSON.parse(response); // 解析JSON數組// 后續渲染邏輯不變...
});
七、跨域問題:原理與解決方案
在AJAX請求中,若前端頁面與后端接口的“協議、域名、端口”三者有任一不同,會觸發瀏覽器的**同源策略**限制,導致請求失敗(跨域錯誤)。理解跨域的產生原因及解決方案是前端開發的必備技能。
1. 同源策略與跨域判定
(1)同源策略定義
:::danger
同源策略是瀏覽器的安全機制,要求協議、域名、端口三者完全一致的兩個資源才能交互(如發送AJAX請求、操作DOM)。其目的是防止惡意網站竊取其他網站的敏感數據(如Cookie、LocalStorage)。
:::
(2)跨域判定示例
前端頁面URL | 后端接口URL | 是否跨域 | 原因分析 |
---|---|---|---|
http://localhost:80/index.html | http://localhost:80/api | 否 | 協議(http)、域名(localhost)、端口(80)均相同 |
http://localhost:80/index.html | https://localhost:80/api | 是 | 協議不同(http vs https) |
http://localhost:80/index.html | http://127.0.0.1:80/api | 是 | 域名不同(localhost vs 127.0.0.1) |
http://localhost:80/index.html | http://localhost:8080/api | 是 | 端口不同(80 vs 8080) |
(3)跨域錯誤表現
瀏覽器控制臺會顯示類似錯誤:
Access to XMLHttpRequest at 'http://api.example.com/data' from origin 'http://localhost:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
(提示:請求被CORS策略阻止,因目標資源未包含Access-Control-Allow-Origin
響應頭)
2. 跨域解決方案詳解
(1)CORS(跨域資源共享):生產環境首選
:::danger
CORS(Cross-Origin Resource Sharing)是W3C標準解決方案,通過后端設置響應頭允許指定源訪問,簡單高效。
:::
① 原理
后端在響應中添加Access-Control-Allow-Origin
頭,聲明允許跨域的前端域名,瀏覽器驗證通過后放行請求。
② 實現方式(后端配置)
- 允許單個域名(如允許
http://localhost:8080
):
// PHP示例
header("Access-Control-Allow-Origin: http://localhost:8080");
- 允許多個域名(通過條件判斷):
$allowedOrigins = ["http://localhost:8080", "http://example.com"];
$origin = $_SERVER["HTTP_ORIGIN"] ?? "";
if (in_array($origin, $allowedOrigins)) {header("Access-Control-Allow-Origin: $origin");
}
- 允許所有域名(不推薦,存在安全風險):
header("Access-Control-Allow-Origin: *"); // 生產環境禁止使用
③ 支持復雜請求(如帶Cookie、自定義頭)
若請求包含Cookie或自定義頭,需額外配置:
// 允許帶Cookie
header("Access-Control-Allow-Credentials: true");
// 允許的請求頭(如Content-Type、Authorization)
header("Access-Control-Allow-Headers: Content-Type, Authorization");
// 允許的請求方法(如GET、POST、PUT)
header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE");
(2)JSONP:兼容舊瀏覽器的GET請求方案
:::danger
JSONP利用<script>
標簽不受同源策略限制的特性,通過動態創建腳本標簽實現跨域請求(僅支持GET方法)。
:::
① 原理
- 前端定義回調函數(如
handleData
),用于接收后端數據; - 動態創建
<script>
標簽,src
指向后端接口,并傳入回調函數名(如http://api.example.com/data?callback=handleData
); - 后端返回
handleData(數據)
格式的JS代碼,前端腳本執行時自動調用回調函數。
② 實現示例
- 前端代碼:
// 定義回調函數
function handleData(data) {console.log("跨域數據:", data); // 處理后端返回的數據
}// 動態創建script標簽發起JSONP請求
const script = document.createElement("script");
script.src = "http://api.example.com/data?callback=handleData"; // 傳入回調名
document.body.appendChild(script);
- 后端代碼(PHP):
$callback = $_GET["callback"]; // 獲取回調函數名("handleData")
$data = ["name" => "小明", "age" => 18]; // 要返回的數據
echo $callback . "(" . json_encode($data) . ")"; // 輸出"handleData({"name":"小明","age":18})"
③ 局限性
- 僅支持GET請求,無法發送POST/PUT等方法;
- 存在安全風險(若后端未校驗回調函數名,可能遭受XSS攻擊);
- 無法捕獲HTTP錯誤狀態碼(如404、500)。
(3)請求代理:開發環境常用方案
:::danger
請求代理通過“同源服務器中轉”實現跨域,適用于前端開發階段(如Vue/React項目)。
:::
① 原理
- 前端頁面(
http://localhost:8080
)向本地代理服務器(同源,無跨域)發送請求; - 代理服務器(如Node.js、Nginx)轉發請求到目標接口(
http://api.example.com
); - 代理服務器將目標接口的響應返回給前端。
② 實現示例(Vue CLI代理配置)
Vue項目中通過vue.config.js
配置代理:
// vue.config.js
module.exports = {devServer: {proxy: {"/api": { // 匹配以/api開頭的請求target: "http://api.example.com", // 目標接口域名changeOrigin: true, // 開啟代理,模擬同源請求pathRewrite: { "^/api": "" } // 移除請求路徑中的/api前綴}}}
};
前端請求時直接調用代理路徑:
// 前端請求(實際會被代理到http://api.example.com/data)
axios.get("/api/data").then(response => { ... });
③ 優勢
- 無需修改后端代碼,前端可直接對接跨域接口;
- 支持所有HTTP方法(GET/POST/PUT等),無JSONP的限制;
- 可在代理層添加額外邏輯(如請求攔截、數據轉換)。
(4)其他方案(了解即可)
- document.domain:適用于主域名相同、子域名不同的場景(如
a.example.com
與b.example.com
),通過設置document.domain = "example.com"
實現同源。 - postMessage:用于兩個不同源的頁面(如iframe與父頁面)之間通信,通過
window.postMessage
發送消息,window.addEventListener("message")
接收。
3. 解決方案選擇建議
場景 | 推薦方案 | 原因分析 |
---|---|---|
生產環境、全方法支持 | CORS | 標準方案,支持所有HTTP方法,安全性高 |
舊瀏覽器、僅GET請求 | JSONP | 兼容IE等舊瀏覽器,實現簡單但功能有限 |
開發環境、前端獨立開發 | 請求代理(如Vue CLI代理) | 無需后端配合,前端自主解決跨域,適合調試 |
主域名相同、子域不同 | document.domain | 簡單高效,僅適用于特定域名結構 |