在现代前端开发中,掌握网络请求、异步处理以及数据操作是构建高效、健壮应用的基础。本文将围绕三个核心主题展开:网页请求方式(Ajax 与 Fetch)的对比与封装、Promise 的深入理解与实践,以及 JavaScript 中的数据拷贝机制(特别是引用式拷贝) ,帮助开发者建立系统性认知。
一、网页请求:从 Ajax 到 Fetch —— 异步通信的演进
1.1 传统 Ajax:基于回调的请求方式
XMLHttpRequest(简称 XHR)是早期浏览器提供的原生 API,用于在不刷新页面的情况下与服务器交换数据。其典型用法如下:
javascript
编辑
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.github.com/users/shunwuyu', true);
xhr.send();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
const data = JSON.parse(xhr.responseText);
console.log(data);
}
};
缺点:
- 代码冗长,逻辑分散;
- 嵌套回调易导致“回调地狱”(Callback Hell);
- 错误处理复杂,需手动判断状态码。
1.2 现代 Fetch:基于 Promise 的简洁方案
fetch() 是 ES6 引入的新一代网络请求 API,天然支持 Promise,语法更简洁:
javascript
编辑
fetch('https://api.github.com/users/shunwuyu')
.then(res => res.json())
.then(data => console.log(data));
优势:
- 链式调用,逻辑清晰;
- 天然支持
.then()、.catch()、.finally(); - 更符合函数式编程思想。
⚠️ 注意:
fetch默认不 reject HTTP 错误状态(如 404、500),需手动检查res.ok。
1.3 封装 getJSON:让 Ajax 支持 Promise
为了统一接口或兼容旧环境,我们可以手动封装一个返回 Promise 的 getJSON 函数,使 Ajax 具备现代异步能力:
javascript
编辑
function getJSON(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.send();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(JSON.parse(xhr.responseText));
} else {
reject(new Error(xhr.statusText));
}
}
}
});
}
// 使用
getJSON('https://api.github.com/users/shunwuyu')
.then(data => console.log(data))
.catch(err => console.error(err))
.finally(() => console.log('请求结束'));
这种封装实现了 “Ajax 的 Promise 化” ,既保留了底层控制力,又享受了现代异步语法的便利。
二、深入理解 Promise:异步流程控制的核心
2.1 Promise 是什么?
Promise 是 ES6 引入的异步编程解决方案,用于将异步操作的结果以同步的方式进行流程控制。它代表一个尚未完成但预期将来会完成的操作。
- 状态:
pending(初始) →fulfilled(成功) 或rejected(失败) - 不可逆:状态一旦改变,无法再变回
pending
2.2 Promise 的构造与使用
javascript
编辑
const p = new Promise((resolve, reject) => {
// 同步执行器(executor)
setTimeout(() => {
Math.random() > 0.5 ? resolve("成功") : reject("失败");
}, 1000);
});
p.then(result => console.log(result))
.catch(error => console.error(error))
.finally(() => console.log("无论成功失败都会执行"));
2.3 实战:手写 sleep 函数
利用 Promise + setTimeout 可轻松实现“延迟执行”:
javascript
编辑
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
// 使用
async function demo() {
console.log("开始");
await sleep(3000);
console.log("3秒后执行");
}
demo();
这展示了 Promise 在流程控制中的强大能力——将异步操作“线性化”。
三、JavaScript 数据拷贝:浅拷贝 vs 深拷贝 vs 引用式拷贝
3.1 内存模型基础
- 栈内存(Stack) :存储基本类型(
number,string,boolean等),值直接保存。 - 堆内存(Heap) :存储引用类型(
object,array,function),变量保存的是指向堆内存的地址。
javascript
编辑
let a = { name: "Alice" };
let b = a; // b 指向 a 的同一块堆内存
b.name = "Bob";
console.log(a.name); // "Bob" —— 修改 b 影响了 a!
这就是引用式拷贝(Reference Copy) :复制的是地址,不是值。
3.2 浅拷贝(Shallow Copy)
只复制对象的第一层属性,嵌套对象仍共享引用。
常用方法:
arr.concat([])[...arr]Object.assign({}, obj)Array.from(arr)
javascript
编辑
const arr = [1, 2, { x: 3 }];
const shallow = [...arr];
shallow[2].x = 99;
console.log(arr[2].x); // 99 —— 嵌套对象被修改!
3.3 深拷贝(Deep Copy)
递归复制所有层级,完全独立。
常用方法:
JSON.parse(JSON.stringify(obj))(有局限性:不能处理函数、undefined、Symbol、循环引用等)- 第三方库(如 Lodash 的
_.cloneDeep) - 手写递归函数
javascript
编辑
const arr = [1, 2, { x: 3 }];
const deep = JSON.parse(JSON.stringify(arr));
deep[2].x = 99;
console.log(arr[2].x); // 3 —— 原数组未受影响
✅ 适用场景:
- 浅拷贝:数据结构简单,无嵌套对象;
- 深拷贝:需要完全隔离的副本(如状态管理、撤销操作)。
总结:构建现代前端开发的知识体系
| 主题 | 核心要点 | 实践建议 |
|---|---|---|
| 网络请求 | Ajax(回调) vs Fetch(Promise) | 优先使用 fetch,必要时封装 getJSON 兼容旧逻辑 |
| Promise | 状态机、链式调用、错误处理 | 用 async/await 简化流程,避免 .then 嵌套 |
| 数据拷贝 | 引用 vs 浅拷贝 vs 深拷贝 | 明确数据结构,选择合适拷贝方式;慎用 JSON.stringify |
掌握这三大模块,不仅能写出更健壮的代码,还能深入理解 JavaScript 的运行机制与异步哲学。真正的前端高手,不仅会用 API,更懂其背后的原理与权衡。
🌟 学习建议:
- 动手封装自己的
ajax、sleep、deepClone工具函数;- 在项目中尝试用
async/await替代.then链;- 调试时多观察内存引用关系,避免“意外共享”。
前端之路,始于细节,成于体系。