JavaScript 学习笔记:Promise、AJAX 与引用拷贝

94 阅读4分钟

JavaScript 学习笔记:Promise、AJAX 与引用拷贝

一、AJAX 与 Fetch 的对比

在现代 Web 开发中,前端经常需要从服务器获取数据。JavaScript 提供了两种主流方式来实现异步请求:AJAX(基于 XMLHttpRequest)Fetch API

  • AJAX 是一种较早的技术,它基于回调函数处理异步操作。开发者需手动监听 onreadystatechange 事件,并判断 readyStatestatus 来确认请求是否成功。
  • 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()

封装思路:

  1. 使用 XMLHttpRequest 发起 GET 请求;
  2. onreadystatechange 中判断请求是否完成且成功;
  3. 成功时调用 resolve(),失败时(如网络错误)调用 reject()
  4. 整个函数返回一个 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 变为 fulfilledrejected,就无法再改变。

  • 链式调用:通过 .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] —— 原数组也被修改!

这是因为 arr2arr 指向同一块堆内存,修改任一变量都会影响另一个。

解决方案:深拷贝

方法 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 FetchAJAX 回调复杂,Fetch 基于 Promise 更简洁
Promise状态管理(pending/fulfilled/rejected),链式调用,解决回调地狱
getJSON 封装用 Promise 包装 AJAX,统一异步接口
引用拷贝对象/数组赋值是引用传递,修改会影响原数据
深拷贝JSON.parse(JSON.stringify()) 是常用但有限制的方案

通过本次学习,我们不仅掌握了异步请求的封装技巧,也深入理解了 JavaScript 内存模型与数据复制机制。这些知识是构建健壮、可维护前端应用的基础。

📌 提示:在实际项目中,推荐优先使用 fetch 或成熟的 HTTP 库(如 Axios),但在理解底层原理时,手写 AJAX + Promise 封装极具价值。