当前位置: 首页 > news >正文

JavaScript手录18-ajax:异步请求与项目上线部署

画板

前言:软件开发流程

AJAX:前端与后端的数据交互

前后端协作基础

Web应用的核心是“数据交互”,前端负责展示与交互,后端负责处理逻辑与数据存储,二者通过网络请求协作。

(1)项目开发流程与岗位分工
  • 核心流程:产品经理(需求)→ UI设计师(界面)→ 前端(可视化)→ 后端(逻辑)→ 测试(验证)→ 运维(上线)。
  • 核心岗位
    • 前端:负责用户可见的界面(HTML/CSS/JS/Vue/React/UI框架例如element-ui/Ant-Design等)、数据展示、用户交互。
    • 后端:处理业务逻辑(如登录验证、订单处理)、操作数据库、提供数据接口。
    • 数据库:存储结构化数据(如用户信息、商品库存),仅允许后端访问。

画板

(2)前后端职责边界
前端职责后端职责
构建用户界面(静态+动态渲染)处理业务逻辑(如权限校验、计算)
收集用户操作(如表单输入、点击)操作数据库(增删改查数据)
发送请求到后端,接收并展示数据提供API接口,返回格式化数据(JSON)
处理前端验证(如表单格式)处理后端验证(如数据合法性、权限)
数据流转全流程

用户操作触发的数据交互需经过“前端→后端→数据库→后端→前端”的完整链路:

  1. 用户操作:如点击“查询商品”按钮。
  2. 前端处理:收集参数(如商品ID),通过AJAX发送请求到后端接口(如/api/goods)。
  3. 后端处理
    • 接收请求,验证参数(如用户权限)。
    • 向后端数据库发送查询命令(如SQL语句)。
  4. 数据库响应:返回符合条件的数据(如商品详情)。
  5. 后端响应:将数据库数据格式化(如JSON),返回给前端。
  6. 前端渲染:接收数据,通过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(由二级域名衍生,无需额外注册)。
(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)。
  • 备案:国内服务器使用域名必须备案(免费,约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后,sendnull
  • 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搭建步骤
  1. 下载安装:从XAMPP官网下载对应系统版本,按提示安装(默认路径即可)。

  1. 启动服务:打开XAMPP控制面板,点击“Start”启动Apache(Web服务器)。

  1. 验证运行:浏览器访问http://localhosthttp://127.0.0.1,出现XAMPP默认页面即成功。

(3)端口号配置

端口号是服务器上区分不同服务的“房间号”,默认端口:

  • HTTP协议:80(可省略,如localhost:80等价于localhost)。
  • HTTPS协议:443。
  • MySQL数据库:3306。

端口冲突解决:若80端口被占用(如被IIS、迅雷占用),可修改Apache端口:

  1. XAMPP控制面板点击“Config”→“Apache (httpd.conf)”。
  2. 搜索Listen 80,改为Listen 8080(或其他未占用端口)。
  3. 重启Apache,访问时需带端口:http://localhost:8080

2. 项目部署:从本地到线上

部署是将开发完成的项目文件上传到服务器,使互联网用户可访问的过程。

(1)本地部署步骤(XAMPP为例)
  1. 放置文件:将项目文件(HTML、CSS、JS等)复制到XAMPP安装目录的htdocs文件夹(如C:\xampp\htdocs\myproject)。

  1. 访问项目:浏览器输入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. 购买云服务器:选择合适配置(如1核2G,1M带宽),操作系统选Linux(如CentOS)。
  2. 安装环境:通过服务器管理面板(如宝塔面板)安装Apache/Nginx、PHP(如需)。
  3. 上传文件:用FTP工具(如FileZilla)将项目文件上传到服务器的网站根目录(如/www/wwwroot/域名)。
  4. 配置域名:在云服务商控制台将域名解析到服务器IP,完成备案(国内服务器)。
  5. 测试访问:通过域名访问项目,检查是否正常运行(如http://www.你的域名.com)。
(3)部署注意事项
  • 路径问题:项目中资源引用需用相对路径(如./css/style.css),避免绝对路径(如C:\...)。
  • 权限设置:服务器文件需设置正确权限(如Linux中chmod 755),避免因权限不足导致访问失败。
  • 备份:定期备份项目文件和数据库,防止数据丢失。
  • HTTPS:通过云服务商申请免费SSL证书(如Let’s Encrypt),开启HTTPS(提升安全性,浏览器地址栏显示锁图标)。
(4)常见问题与解决
  • 端口冲突(启动失败,日志显示“Port 80 in use”):
    1. 原因:80端口被其他程序(如IIS、迅雷、Skype)占用;
    2. 解决:
      • 关闭占用端口的程序(通过“任务管理器”结束进程);
      • 修改Apache端口:XAMPP控制面板→“Config”→“Apache (httpd.conf)”,搜索 Listen 80 改为 Listen 8080,重启Apache后通过 http://localhost:8080 访问。
  • 权限问题(无法访问项目文件):
    1. 确保项目文件放在XAMPP的 htdocs 目录下(如 C:\xampp\htdocs\myproject);
    2. 首次打开 htdocs 时,在VS Code中点击“信任父文件夹”授权编辑。
  • 开发注意
    • 必须通过服务器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方式),参数名需与前端完全一致。

示例:接收前端的 usernamehobby 参数并返回对应响应

<?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/userPOST /api/login),前端需严格遵循。

一个前后端交互的-get请求示例

五、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(副本修改成功)

局限性

  • 无法复制函数、SymbolDate(会转为字符串)、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.htmlhttp://localhost:80/api协议(http)、域名(localhost)、端口(80)均相同
http://localhost:80/index.htmlhttps://localhost:80/api协议不同(http vs https)
http://localhost:80/index.htmlhttp://127.0.0.1:80/api域名不同(localhost vs 127.0.0.1)
http://localhost:80/index.htmlhttp://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方法)。

:::

① 原理
  1. 前端定义回调函数(如handleData),用于接收后端数据;
  2. 动态创建<script>标签,src指向后端接口,并传入回调函数名(如http://api.example.com/data?callback=handleData);
  3. 后端返回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项目)。

:::

① 原理
  1. 前端页面(http://localhost:8080)向本地代理服务器(同源,无跨域)发送请求;
  2. 代理服务器(如Node.js、Nginx)转发请求到目标接口(http://api.example.com);
  3. 代理服务器将目标接口的响应返回给前端。
② 实现示例(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.comb.example.com),通过设置document.domain = "example.com"实现同源。
  • postMessage:用于两个不同源的页面(如iframe与父页面)之间通信,通过window.postMessage发送消息,window.addEventListener("message")接收。

3. 解决方案选择建议

场景推荐方案原因分析
生产环境、全方法支持CORS标准方案,支持所有HTTP方法,安全性高
旧浏览器、仅GET请求JSONP兼容IE等旧浏览器,实现简单但功能有限
开发环境、前端独立开发请求代理(如Vue CLI代理)无需后端配合,前端自主解决跨域,适合调试
主域名相同、子域不同document.domain简单高效,仅适用于特定域名结构
http://www.dtcms.com/a/340285.html

相关文章:

  • AI 自动化编程 trae 体验 页面添加富编辑器
  • (5)软件包管理器 yum | Vim 编辑器 | Vim 文本批量化操作 | 配置 Vim
  • 深度解析:RESTful API中的404错误 - 不是所有404都是Bug
  • Vue 3项目中的路由管理和状态管理系统
  • 【Day 31】Linux-LNMP
  • MySQL基础操作
  • SpringBoot + MyBatis-Plus 使用 listObjs 报 ClassCastException 的原因与解决办法
  • Rabbit 实战指南-学习笔记
  • HTML+CSS:浮动详解
  • 3D文档控件Aspose.3D实用教程:使用 C# 构建 OBJ 到 U3D 转换器
  • awk 基础用法示例
  • 测试DuckDB插件对不同格式xlsx文件的读写效率
  • MyCAT分库分表
  • Go特有的安全漏洞及渗透测试利用方法(通俗易懂)
  • 次短路P2865 [USACO06NOV] Roadblocks G题解
  • SLAM文献之-Globally Consistent and Tightly Coupled 3D LiDAR Inertial Mapping
  • RESP协议
  • React响应式链路
  • SCAU学习笔记 - 自科三面前端方向实战演示
  • 157-基于Python的懂车帝汽车数据爬虫分析与可视化系统
  • NVIDIA Isaac Sim
  • Ubuntu 主机名:精通配置与管理
  • 全球首款 8K 全景无人机影翎 A1 发布解读:航拍进入“先飞行后取景”时代
  • 从 “模仿” 到 “创造”:AI 大模型的 “思维进化” 背后,技术突破在哪?
  • 沪深股指期货指数「IF000」期货行情怎么看?
  • 利用无事务方式插入数据库解决并发插入问题(最小主键id思路)
  • 海外短剧app、h5、独立站、国内短剧看广告app,短剧小程序、源码交付开发
  • java17学习笔记
  • RK android14 Setting一级菜单IR遥控器无法聚焦问题解决方法
  • VPS海外节点性能监控全攻略:从基础配置到高级优化