JavaScript 学习笔记:Promise、AJAX 与引用拷贝
一、AJAX 与 Fetch 的对比
在现代 Web 开发中,前端经常需要从服务器获取数据。JavaScript 提供了两种主流方式来实现异步请求:AJAX(基于 XMLHttpRequest) 和 Fetch API。
- AJAX 是一种较早的技术,它基于回调函数处理异步操作。开发者需手动监听
onreadystatechange事件,并判断readyState和status来确认请求是否成功。 - Fetch 则是 ES6 引入的现代方案,它基于 Promise 实现,语法更简洁、语义更清晰。例如:
fetch('https://api.github.com/users/shunwuyu')
.then(res => res.json())
.then(data => console.log(data));
相比 AJAX,Fetch 不再依赖回调嵌套,避免了“回调地狱”,代码结构更易读、维护性更强。
二、封装 getJSON 函数(基于 AJAX + Promise)
为了统一数据请求逻辑,我们可以封装一个 getJSON 函数,使用原生 AJAX 实现,但对外暴露 Promise 接口,使其支持 .then() 和 .catch()。
封装思路:
- 使用
XMLHttpRequest发起 GET 请求; - 在
onreadystatechange中判断请求是否完成且成功; - 成功时调用
resolve(),失败时(如网络错误)调用reject(); - 整个函数返回一个
Promise实例。
实现代码:
const 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 && xhr.status === 200) {
const data = JSON.parse(xhr.responseText);
resolve(data);
}
};
xhr.onerror = function () {
reject('请求失败');
};
});
};
使用示例:
getJSON('https://api.github.com/users/shunwuyu')
.then(data => console.log(data))
.catch(err => console.error(err));
✅ 这样封装后,即使底层使用的是传统的 AJAX,上层调用者也能享受 Promise 带来的链式调用和错误处理便利。
三、深入理解 Promise
Promise 是 ES6 引入的用于处理异步操作的对象,它代表一个未来可能完成或失败的操作及其结果。
核心特性:
-
三种状态:
pending(初始状态)fulfilled(操作成功)rejected(操作失败)
-
状态不可逆:一旦从
pending变为fulfilled或rejected,就无法再改变。 -
链式调用:通过
.then()处理成功结果,.catch()捕获错误,.finally()无论成功与否都会执行。
构造函数:
new Promise((resolve, reject) => {
// 异步操作
if (success) resolve(value);
else reject(error);
});
示例:模拟延迟(sleep 函数)
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
sleep(3000)
.then(() => console.log('3秒后输出'))
.finally(() => console.log('执行完成'));
这个 sleep 函数常用于测试异步流程控制,体现了 Promise 如何将定时器这类异步操作“包装”成可链式调用的形式。
四、引用式拷贝与深拷贝
在 JavaScript 中,变量存储分为 栈内存 和 堆内存:
- 基本类型(如 number、string、boolean)直接存储在栈中;
- 引用类型(如 object、array)在栈中存储的是指向堆内存的引用地址。
问题:引用式拷贝
const arr = [1, 2, 3];
const arr2 = arr; // 引用拷贝
arr2[0] = 999;
console.log(arr); // [999, 2, 3] —— 原数组也被修改!
这是因为 arr2 和 arr 指向同一块堆内存,修改任一变量都会影响另一个。
解决方案:深拷贝
方法 1:JSON.parse(JSON.stringify())
适用于纯 JSON 数据(不含函数、undefined、Symbol 等):
const arr2 = JSON.parse(JSON.stringify(arr));
方法 2:数组的 concat() 或扩展运算符(浅拷贝)
const arr3 = [].concat(arr); // 或 [...arr]
⚠️ 注意:
concat()和扩展运算符仅对一层嵌套有效,属于浅拷贝。若数组包含对象,内部对象仍是引用。
方法 3:递归实现深拷贝(略复杂,此处不展开)
五、综合实践:结合 GitHub API 获取用户信息
以 GitHub 用户 shunwuyu 为例(来自提供的 JSON 数据):
getJSON('https://api.github.com/users/shunwuyu')
.then(user => {
console.log(`用户名: ${user.name}`);
console.log(`所在地: ${user.location}`); // 江西南昌
console.log(`公开仓库数: ${user.public_repos}`); // 213
})
.catch(err => console.error('获取用户信息失败:', err));
这展示了如何将封装的 getJSON 应用于真实场景,同时体现 Promise 在数据流处理中的优势。
六、总结
| 概念 | 关键点 |
|---|---|
| AJAX vs Fetch | AJAX 回调复杂,Fetch 基于 Promise 更简洁 |
| Promise | 状态管理(pending/fulfilled/rejected),链式调用,解决回调地狱 |
| getJSON 封装 | 用 Promise 包装 AJAX,统一异步接口 |
| 引用拷贝 | 对象/数组赋值是引用传递,修改会影响原数据 |
| 深拷贝 | JSON.parse(JSON.stringify()) 是常用但有限制的方案 |
通过本次学习,我们不仅掌握了异步请求的封装技巧,也深入理解了 JavaScript 内存模型与数据复制机制。这些知识是构建健壮、可维护前端应用的基础。
📌 提示:在实际项目中,推荐优先使用
fetch或成熟的 HTTP 库(如 Axios),但在理解底层原理时,手写 AJAX + Promise 封装极具价值。