前端新人别慌:手写原生AJAX请求,彻底搞懂它到底在干啥(附避坑指南)

前端新人别慌:手写原生AJAX请求,彻底搞懂它到底在干啥(附避坑指南)

说实话,我刚入行那会儿,AJAX这个词听着就挺唬人的。什么"Asynchronous JavaScript and XML",念都念不顺溜,感觉像是某种黑魔法。那时候我天天用jQuery的$.ajax,几行代码数据就来了,爽得很。直到有一天面试官问我:"如果不用任何库,你怎么发请求?“我当场就懵了,脑子里一片空白,支支吾吾半天憋出一句"用fetch?”——然后就没有然后了。

所以如果你现在也是这个状态,别慌,太正常了。这篇文章咱们就彻底扒开AJAX的底裤,看看它到底在搞什么飞机。不用axios,不用fetch,就拿最原始的XMLHttpRequest,从零手写一遍。等你搞懂这些,再去用那些封装好的库,你会发现自己看问题的角度完全不一样了。

AJAX这名字到底啥意思?跟后端接口啥关系?

先说说这个拗口的名字。AJAX全称是"Asynchronous JavaScript and XML",翻译过来就是"异步的JavaScript和XML"。这名字现在看其实有点尴尬,因为:

  1. 异步这部分是对的,AJAX确实是异步操作
  2. XML这部分就扯淡了,现在谁还用XML传数据啊,不都是JSON吗

所以你可以这么理解:AJAX就是一种在不刷新整个页面的情况下,跟服务器偷偷摸摸交换数据的技术。注意这个"偷偷摸摸",用户是无感知的,页面不会白屏一下,数据就悄悄来了。

那它跟后端接口啥关系?简单说,AJAX是前端手里的"电话",后端接口是"电话号码"。你拿着电话(AJAX)拨号(发请求),对方(后端)接电话返回数据。没有电话你打不了,没有号码你也不知道打给谁。

这里有个误区要纠正:很多人以为AJAX是某个具体的函数或者库,其实不是。AJAX是一种技术方案,不是某个具体的工具。你用XMLHttpRequest实现叫AJAX,用fetch实现也叫AJAX,甚至早期用iframe偷偷加载数据也算AJAX。所以下次面试官问"什么是AJAX",别背定义,说"一种异步通信方案"就到位了。

XMLHttpRequest:上古神器还是真香工具?

现在提到XMLHttpRequest(以下简称XHR),很多新人一脸嫌弃:"都2026年了,还用这老古董?"确实,fetch API更现代、Promise-based、代码更简洁。但你要是因为这个就不学XHR,那真的是丢了西瓜捡芝麻

为啥?因为所有现代HTTP库底层都是XHR。axios、superagent、甚至fetch的polyfill,底层全是XHR。你不理解XHR,就像开车不懂发动机原理——平时没事,一旦抛锚你就傻眼。

而且XHR有些特性是fetch目前还不太好替代的:

  • 进度监控(上传下载进度条)
  • 同步请求(虽然不建议用,但某些场景需要)
  • 请求取消(fetch直到最近才支持AbortController)

所以咱们今天就拿XHR开刀,看看这"老古董"到底香不香。

四行代码搞定一个请求?先别急着抄

网上很多教程说"四行代码搞定AJAX",确实能跑,但那种写法生产环境用就是找死。咱们先看看最简版本长啥样,然后再慢慢加料。

// 最简版本——看看就行,别真这么写
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data');
xhr.onload = () => console.log(xhr.responseText);
xhr.send();

这四行确实能发请求,但问题太多了:

  • 出错怎么办?没处理
  • 状态码判断呢?没有
  • 请求头设置呢?也没有
  • 超时控制?想多了

所以咱们得把这四行扩展成能用在项目里的版本。别急,一步一步来。

open、send、onload 到底谁先谁后?

XHR的工作流程其实挺像点外卖的:

  1. new XMLHttpRequest() = 拿出手机
  2. open() = 打开外卖APP,选好商家(配置请求参数)
  3. setRequestHeader() = 备注"不要香菜"(设置请求头)
  4. send() = 点击下单(真正发送请求)
  5. onload/onerror = 外卖到了/骑手迷路了(回调处理)

顺序绝对不能乱!我见过新手写成这样:

// 错误示范!别这么写
const xhr = new XMLHttpRequest();
xhr.send();  // 还没open就send?报错!
xhr.open('GET', 'https://api.example.com/data');

正确的顺序必须是:open → 设置请求头 → send → 等待回调

const xhr = new XMLHttpRequest();
// 第一步:open,初始化请求
xhr.open('GET', 'https://jsonplaceholder.typicode.com/posts/1', true);

// 第二步:设置请求头(如果需要)
xhr.setRequestHeader('Content-Type', 'application/json');

// 第三步:send,发送请求
// GET请求没有body,传null;POST请求这里传数据
xhr.send(null);

// 第四步:监听状态变化
xhr.onload = function() {
  if (xhr.status === 200) {
    console.log('成功:', JSON.parse(xhr.responseText));
  } else {
    console.error('服务器返回错误:', xhr.statusText);
  }
};

xhr.onerror = function() {
  console.error('网络请求失败,检查网络连接');
};

看到那个open的第三个参数true了吗?那是异步开关。如果写成false,就变成同步请求了。千万别这么干!下面咱们专门说说同步请求为啥是洪水猛兽。

同步请求:千万别在主线程玩这个!

有些教程为了"简化代码",会教你把open的第三个参数设为false,变成同步请求。代码看起来确实简单了,不用回调,直接拿到结果:

// 危险!同步请求,会阻塞页面
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', false); // false=同步
xhr.send();
console.log(xhr.responseText); // 直接拿到结果

看起来爽是吧?但代价是整个浏览器标签页卡死,直到请求回来。如果网络不好,用户就会看到一个 frozen 的页面,鼠标点不动,滚动条拉不了,甚至连关闭按钮都没反应。

现代浏览器已经强烈不推荐同步请求了,Chrome还计划在未来的版本里彻底禁掉主线程的同步XHR。所以记住:永远传true,或者干脆不传(默认就是true)

那同步请求就完全没用吗?也不是。在Web Worker里面可以用,因为Worker没有UI,阻塞了也不影响页面。但那是进阶话题了,新手先别碰。

readyState的五个阶段:你的请求卡在哪了?

XHR有个属性叫readyState,表示请求的当前阶段。很多新手搞不懂这个和status的区别。简单说:

  • readyState = 请求进行到哪个步骤了(过程)
  • status = 服务器返回的HTTP状态码(结果)

readyState有五个值,从0到4:

const xhr = new XMLHttpRequest();

console.log(xhr.readyState); // 0,刚创建,还没open

xhr.open('GET', 'https://jsonplaceholder.typicode.com/posts/1', true);
console.log(xhr.readyState); // 1,open调用了

xhr.onreadystatechange = function() {
  // 这个回调会在每次readyState变化时触发
  console.log('当前状态:', xhr.readyState);
  
  if (xhr.readyState === 2) {
    console.log('已经send,服务器收到请求了');
  }
  if (xhr.readyState === 3) {
    console.log('正在下载响应体,可能只收到一部分数据');
  }
  if (xhr.readyState === 4) {
    console.log('全部搞定,数据下完了');
    // 这时候才能安全地访问responseText
  }
};

xhr.send();

具体对应关系:

  • 0 (UNSENT):刚new出来,啥也没干
  • 1 (OPENED)open()调用了
  • 2 (HEADERS_RECEIVED)send()调用了,服务器返回响应头
  • 3 (LOADING):正在下载响应体(大数据量时可能停留很久)
  • 4 (DONE):全部完成,数据到手

实际开发中,你大部分时候只需要关心readyState === 4,因为这时候才能确定数据完整了。不过如果你想做个进度条,那readyState === 3就有用了,可以配合onprogress事件一起用。

status 200就万事大吉?太天真了!

很多人判断请求成功就写if (xhr.status === 200),这在简单场景没问题,但生产环境太粗糙了。HTTP状态码有一大家子,你得知道怎么处理:

xhr.onload = function() {
  // 别只判断200,2xx都算成功
  if (xhr.status >= 200 && xhr.status < 300) {
    console.log('成功:', xhr.response);
  } 
  // 400系列是客户端错误(你的锅)
  else if (xhr.status >= 400 && xhr.status < 500) {
    if (xhr.status === 401) {
      console.error('没登录,去登录页吧');
      window.location.href = '/login';
    } else if (xhr.status === 403) {
      console.error('权限不够,联系管理员');
    } else if (xhr.status === 404) {
      console.error('接口不存在,检查URL');
    } else {
      console.error('客户端错误:', xhr.status);
    }
  }
  // 500系列是服务器错误(后端的锅)
  else if (xhr.status >= 500) {
    console.error('服务器挂了,找后端算账:', xhr.status);
  }
};

常见状态码速查:

  • 200 OK:万事大吉
  • 201 Created:创建成功(POST新建资源)
  • 204 No Content:成功但无返回体(DELETE常用)
  • 301/302:重定向(XHR会自动跟随,一般不用管)
  • 400 Bad Request:参数传错了,检查你的body
  • 401 Unauthorized:没登录或token过期
  • 403 Forbidden:登录了但权限不够
  • 404 Not Found:URL写错了
  • 500 Internal Server Error:后端代码报错了
  • 502 Bad Gateway:网关挂了,可能是Nginx配置问题
  • 503 Service Unavailable:服务维护中

还有个点要注意:status为0的情况。这通常表示请求根本没发出去,可能是跨域被拦了、请求被取消了、或者网络断开。别当成200处理,也别当成500,单独处理:

if (xhr.status === 0) {
  console.error('请求未发送,检查跨域或网络');
}

responseText vs response:别用混了

XHR有两个拿数据的属性,新手经常搞混:

  • responseText:返回纯文本字符串
  • response:根据responseType自动解析后的数据

默认情况下responseType是空字符串,这时候responseresponseText是一样的,都是字符串。但你可以手动设置responseType,让XHR自动帮你解析:

const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://jsonplaceholder.typicode.com/posts/1', true);

// 关键:告诉XHR我要JSON,你帮我自动解析
xhr.responseType = 'json';

xhr.onload = function() {
  if (xhr.status === 200) {
    // 因为设置了responseType='json',这里直接拿到对象
    console.log(xhr.response); // {userId: 1, id: 1, title: "...", body: "..."}
    console.log(xhr.response.title); // 直接点属性,不用JSON.parse
    
    // 如果没设置responseType,就得手动解析
    // const data = JSON.parse(xhr.responseText);
  }
};

xhr.send();

支持的responseType有:

  • ''(默认):字符串
  • 'text':文本字符串(跟默认一样)
  • 'json':自动解析为JSON对象
  • 'blob':二进制大对象(下载文件用)
  • 'arraybuffer':原始二进制数据
  • 'document':XML文档(现在很少用了)

建议:现代浏览器都支持responseType='json',能省掉一次JSON.parse(),还能避免解析错误。但记得做兼容处理,IE11不支持这个。

JSON.parse()翻车现场:后端返回的不一定是JSON

说到JSON解析,这简直是新手噩梦现场。你信心满满地JSON.parse(xhr.responseText),结果报错了,一看后端返回的数据:

// 你以为后端返回的:
{"code": 200, "data": {"name": "张三"}}

// 实际后端返回的:
{code: 200, data: {name: "张三"}}  // 没引号,不是标准JSON
// 或者:
console.log("debug");{"code": 200, "data": {}}  // 多了个console.log
// 或者:
{"code": 200, "data": null}  // 标准JSON,但你的代码没做null判断

所以解析前一定要try-catch,并且做数据校验:

xhr.onload = function() {
  if (xhr.status === 200) {
    let data;
    try {
      data = JSON.parse(xhr.responseText);
    } catch (e) {
      console.error('JSON解析失败,后端返回的不是标准JSON:', xhr.responseText);
      return;
    }
    
    // 即使解析成功,也要检查数据结构
    if (!data || typeof data !== 'object') {
      console.error('数据格式不对:', data);
      return;
    }
    
    // 检查业务状态码(有些接口HTTP 200但业务报错)
    if (data.code !== 200) {
      console.error('业务错误:', data.message);
      return;
    }
    
    console.log('真正可用的数据:', data.data);
  }
};

看到最后那个data.data了吗?很多接口设计是包裹两层的:外层是HTTP协议层,内层是业务状态层。即使HTTP状态是200,业务可能还是失败的,得看里面的code字段。

跨域:前端根本解决不了,但你可以这样调试

“Blocked by CORS policy”——这个报错每个前端都见过,而且第一反应都是:"我代码写错了?"其实跨域错误99%不是前端的问题,是后端没配置CORS(跨域资源共享)。

简单说,浏览器有个安全策略叫同源策略(Same-Origin Policy),要求协议、域名、端口三者完全一致。你前端跑在localhost:3000,后端在localhost:8080,端口不同,就是跨域,浏览器默认会拦住响应。

但注意:跨域请求其实是发出去了,后端也收到了,也返回了数据,只是浏览器把响应拦下来了,不让你看。你可以在Network面板看到请求,但代码里拿不到数据。

前端能做什么?几乎啥也做不了。这是浏览器的安全机制,你改不了。你能做的只有:

  1. 开发环境配置代理(webpack devServer的proxy)
  2. 让后端加CORS头(Access-Control-Allow-Origin: *)
  3. JSONP(只支持GET,老项目可能还在用)

如果你急着调试,可以用浏览器插件临时禁用CORS(比如Chrome的"CORS Unblock"),但千万别用在生产环境

调试跨域问题的小技巧:先看Network面板的请求有没有发出去,再看Response有没有内容。如果有请求没响应,是后端没收到;如果有请求有响应但代码报错,才是CORS问题。

封装一个能用的AJAX函数:带超时、自动解析、错误处理

好了,基础概念都过了一遍,现在咱们封装一个能在项目里用的AJAX函数。需求:

  • 支持Promise(告别回调地狱)
  • 自动JSON解析
  • 超时控制(默认10秒)
  • 统一错误处理
  • 支持取消请求
function request(options) {
  return new Promise((resolve, reject) => {
    const {
      method = 'GET',
      url,
      data = null,
      headers = {},
      responseType = 'json',
      timeout = 10000 // 默认10秒超时
    } = options;
    
    const xhr = new XMLHttpRequest();
    
    // 超时处理
    xhr.timeout = timeout;
    xhr.ontimeout = () => {
      reject(new Error(`请求超时: ${url}`));
    };
    
    // 网络错误处理
    xhr.onerror = () => {
      reject(new Error(`网络请求失败: ${url}`));
    };
    
    // 状态处理
    xhr.onreadystatechange = () => {
      if (xhr.readyState !== 4) return;
      
      // 请求完成,清除超时计时器(如果有的话)
      
      if (xhr.status >= 200 && xhr.status < 300) {
        // 成功,但还要检查数据有效性
        let result = xhr.response;
        
        // 如果没设置responseType或浏览器不支持,手动解析
        if (responseType === 'json' && typeof result === 'string') {
          try {
            result = JSON.parse(result);
          } catch (e) {
            reject(new Error('JSON解析失败'));
            return;
          }
        }
        
        resolve(result);
      } else {
        // 根据状态码给不同错误信息
        let message = `HTTP ${xhr.status}`;
        if (xhr.status === 0) message = '网络错误或未跨域';
        else if (xhr.status === 401) message = '未授权';
        else if (xhr.status === 403) message = '禁止访问';
        else if (xhr.status === 404) message = '接口不存在';
        else if (xhr.status >= 500) message = '服务器错误';
        
        reject(new Error(message));
      }
    };
    
    // 打开连接
    xhr.open(method, url, true);
    
    // 设置responseType
    xhr.responseType = responseType;
    
    // 设置请求头
    Object.keys(headers).forEach(key => {
      xhr.setRequestHeader(key, headers[key]);
    });
    
    // 如果是POST/PUT,设置默认Content-Type
    if (data && !headers['Content-Type'] && !(data instanceof FormData)) {
      xhr.setRequestHeader('Content-Type', 'application/json');
    }
    
    // 发送数据
    // 如果是对象且不是FormData,转成JSON字符串
    let body = data;
    if (data && typeof data === 'object' && !(data instanceof FormData)) {
      body = JSON.stringify(data);
    }
    
    xhr.send(body);
  });
}

// 使用示例
request({
  method: 'GET',
  url: 'https://jsonplaceholder.typicode.com/posts/1'
}).then(data => {
  console.log('拿到数据:', data);
}).catch(err => {
  console.error('出错了:', err.message);
});

// POST示例
request({
  method: 'POST',
  url: 'https://jsonplaceholder.typicode.com/posts',
  data: {
    title: 'foo',
    body: 'bar',
    userId: 1
  }
}).then(data => {
  console.log('创建成功:', data);
});

这个封装已经能满足大部分场景了。但还缺个重要功能:取消请求

取消请求:AbortController真香警告

有时候用户点了查询按钮,等不及又点了一次,或者页面跳转时还有请求没完成。这时候需要取消之前的请求,避免资源浪费或数据混乱。

XHR原生支持abort()方法,但配合Promise需要额外处理:

function requestWithCancel(options) {
  const xhr = new XMLHttpRequest();
  
  // 把xhr实例暴露出去,方便外部调用abort
  const promise = new Promise((resolve, reject) => {
    // ... 前面的代码一样
    
    xhr.onabort = () => {
      reject(new Error('请求被取消'));
    };
    
    // ... 后面的代码一样
  });
  
  // 给promise加个abort方法
  promise.abort = () => {
    xhr.abort();
  };
  
  return promise;
}

// 使用
const req = requestWithCancel({
  url: 'https://api.example.com/slow-endpoint'
});

// 5秒后还没响应就取消
setTimeout(() => {
  req.abort();
}, 5000);

req.then(data => {
  console.log(data);
}).catch(err => {
  if (err.message === '请求被取消') {
    console.log('用户取消了请求');
  }
});

如果你用AbortController(现代浏览器支持),代码更优雅:

function requestWithAbortController(options) {
  const controller = new AbortController();
  const signal = controller.signal;
  
  const promise = new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    
    // 监听abort信号
    signal.addEventListener('abort', () => {
      xhr.abort();
    });
    
    xhr.onabort = () => {
      reject(new Error('Aborted'));
    };
    
    // ... 其他代码不变
    
    xhr.send();
  });
  
  // 返回promise和controller
  return {
    promise,
    abort: () => controller.abort()
  };
}

// 使用
const { promise, abort } = requestWithAbortController({
  url: 'https://api.example.com/data'
});

// 随时调用abort取消
document.getElementById('cancelBtn').addEventListener('click', abort);

promise.then(data => {
  console.log(data);
}).catch(err => {
  if (err.name === 'AbortError' || err.message === 'Aborted') {
    console.log('请求已取消');
  }
});

请求发不出去?自查清单别急着甩锅后端

遇到请求没响应,很多前端第一反应是"后端又挂了"。但先别急着甩锅,按这个清单自查一遍:

1. URL写对了吗?

// 错误:少了协议
xhr.open('GET', 'api.example.com/data'); 

// 错误:多了空格
xhr.open('GET', ' https://api.example.com/data');

// 正确
xhr.open('GET', 'https://api.example.com/data');

// 本地开发注意:localhost vs 127.0.0.1
// 虽然都能访问,但浏览器认为是不同域名,cookie可能不共享

2. 请求头Content-Type和body格式对不上?

// 错误:发送JSON但Content-Type没设置
xhr.send(JSON.stringify({name: 'test'})); 
// 后端收到的可能是空数据,因为默认Content-Type是text/plain

// 正确
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify({name: 'test'}));

3. 后端返回的数据不干净?
有些后端同学喜欢在接口里加console.log调试,或者返回了BOM头(UTF-8 BOM),导致前端JSON解析失败。让后端检查返回体是否纯JSON,前面不能有任何其他字符。

4. 浏览器缓存搞事情?
GET请求容易被浏览器缓存,特别是IE。解决办法是加随机数:

// 加时间戳防止缓存
const url = `https://api.example.com/data?t=${Date.now()}`;
// 或者加随机数
const url = `https://api.example.com/data?r=${Math.random()}`;

5. HTTPS证书问题?
如果是自签名证书,浏览器会拦截,XHR直接失败。开发环境可以临时允许不安全内容,生产环境必须配正规证书。

几个骚但实用的技巧

用FormData上传文件,比你想象中简单

以前上传文件要用flash或者各种插件,现在XHR+FormData原生支持:

// HTML: <input type="file" id="fileInput" multiple>

document.getElementById('uploadBtn').addEventListener('click', () => {
  const fileInput = document.getElementById('fileInput');
  const files = fileInput.files;
  
  if (files.length === 0) {
    alert('先选文件');
    return;
  }
  
  const formData = new FormData();
  
  // 可以追加多个文件
  for (let i = 0; i < files.length; i++) {
    formData.append('files', files[i]);
  }
  
  // 还可以加其他字段
  formData.append('description', '我的文件');
  
  const xhr = new XMLHttpRequest();
  
  // 上传进度监控
  xhr.upload.onprogress = (e) => {
    if (e.lengthComputable) {
      const percent = (e.loaded / e.total) * 100;
      console.log(`上传进度: ${percent.toFixed(2)}%`);
      // 更新进度条UI
      document.getElementById('progress').style.width = percent + '%';
    }
  };
  
  xhr.onload = () => {
    if (xhr.status === 200) {
      console.log('上传成功');
    }
  };
  
  xhr.open('POST', 'https://api.example.com/upload', true);
  
  // 注意:不要手动设置Content-Type,浏览器会自动设置并加上boundary
  // xhr.setRequestHeader('Content-Type', 'multipart/form-data'); // 错误!
  
  xhr.send(formData);
});

关键点:不要手动设置Content-Type,浏览器会自动生成multipart/form-data; boundary=----WebKitFormBoundary...,手动设置反而会导致boundary丢失,后端解析失败。

模拟进度条:下载也能监控

上传有xhr.upload.onprogress,下载也有xhr.onprogress

const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://example.com/large-file.zip', true);

// 下载进度
xhr.onprogress = (e) => {
  if (e.lengthComputable) {
    const percent = (e.loaded / e.total) * 100;
    console.log(`下载进度: ${percent.toFixed(2)}%`);
    
    // 计算下载速度
    const speed = e.loaded / ((Date.now() - startTime) / 1000);
    console.log(`速度: ${(speed / 1024 / 1024).toFixed(2)} MB/s`);
  } else {
    // 如果服务器没返回Content-Length,只能知道下载了多少,不知道总共多少
    console.log(`已下载: ${(e.loaded / 1024 / 1024).toFixed(2)} MB`);
  }
};

const startTime = Date.now();
xhr.send();

多个请求串行还是并行?

有时候需要发多个请求,要看是串行(一个完了再发下一个)还是并行(同时发):

// 并行:用Promise.all
function request(url) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);
    xhr.onload = () => resolve(xhr.response);
    xhr.onerror = reject;
    xhr.send();
  });
}

// 同时发三个请求,等全部完成
Promise.all([
  request('https://api.example.com/user'),
  request('https://api.example.com/orders'),
  request('https://api.example.com/messages')
]).then(([user, orders, messages]) => {
  console.log('全部搞定:', user, orders, messages);
}).catch(err => {
  console.error('有一个失败了:', err);
});

// 串行:用async/await
async function fetchSequentially() {
  try {
    const user = await request('https://api.example.com/user');
    // 等user回来,再根据user.id查订单
    const orders = await request(`https://api.example.com/orders?userId=${user.id}`);
    // 再等orders回来
    const details = await request(`https://api.example.com/orders/${orders[0].id}`);
    console.log(details);
  } catch (err) {
    console.error(err);
  }
}

别人用fetch你就焦虑?其实底层都一样

看到同事用fetch写代码很优雅,你是不是也焦虑过?其实fetch底层也是XHR(或者说和XHR同一级别的API),只是包装得更现代。理解了XHR,fetch上手就是5分钟的事:

// XHR写法
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', true);
xhr.responseType = 'json';
xhr.onload = () => {
  if (xhr.status === 200) {
    console.log(xhr.response);
  }
};
xhr.send();

// 等价的fetch写法
fetch('https://api.example.com/data')
  .then(res => {
    if (!res.ok) throw new Error(res.statusText);
    return res.json();
  })
  .then(data => console.log(data))
  .catch(err => console.error(err));

fetch的痛点(比如进度监控、同步请求、IE兼容),XHR都能解决。所以先学XHR不吃亏,反而是捷径。

最后说句掏心窝子的

我知道,现在让你手写AJAX,你可能会想:"有axios不用,有病吧?"确实,生产环境直接用axios、fetch或者更现代的@tanstack/react-query就行,没人让你天天造轮子。

但问题是,如果你不理解XHR,你连axios的拦截器都配不明白。为啥请求超时了没触发拦截器?为啥上传进度监听不到?为啥有时候返回的数据格式不对?这些问题的答案都在XHR里。

就像会开车不等于懂发动机,但万一路上抛锚,你会修啊!而且去面试的时候,你能从XHR讲到Promise讲到async/await,再讲到HTTP协议和浏览器事件循环,这不比背八股文强多了?

所以别嫌XHR老,把它当成前端网络请求的"操作系统",axios只是上面的"应用软件"。操作系统懂了,用什么软件都顺手。

好了,这篇文章够长了,代码也给了不少。建议你自己动手敲一遍,特别是那个封装的request函数,改改参数、故意制造点错误,看看报错信息长啥样。纸上得来终觉浅,绝知此事要躬行——这话虽然土,但真管用。

下次面试官再问你"不用框架怎么发请求",希望你能自信地说:“用XMLHttpRequest,先open配置参数,设置回调处理onload和onerror,然后send发送,注意要处理readyState和status的不同状态…”——然后你就看到他点头了。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值