JavaScript手录18-ajax:异步请求与项目上线部署
前言:软件开发流程
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 | 简单高效,仅适用于特定域名结构 |