复习学习javascript之:深入理解网页请求、Promise 异步编程与 JavaScript 拷贝机制

54 阅读4分钟

在现代前端开发中,掌握网络请求、异步处理以及数据操作是构建高效、健壮应用的基础。本文将围绕三个核心主题展开:网页请求方式(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) :存储基本类型(numberstringboolean 等),值直接保存。
  • 堆内存(Heap) :存储引用类型(objectarrayfunction),变量保存的是指向堆内存的地址
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,更懂其背后的原理与权衡。

🌟 学习建议

  • 动手封装自己的 ajaxsleepdeepClone 工具函数;
  • 在项目中尝试用 async/await 替代 .then 链;
  • 调试时多观察内存引用关系,避免“意外共享”。

前端之路,始于细节,成于体系。