Axios 实例创建完全指南:深入 axios.create() 的预配置与隔离机制
本文基于官方文档 Creating an instance 展开,系统讲解 Axios 中 axios.create() 实例机制:如何为不同服务创建预配置实例、实例与默认对象的关系、按请求覆盖默认值的优先级规则,并结合仓库源码揭示 createInstance 的工厂实现、配置合并(mergeConfig)与拦截器隔离的底层原理,帮助你在多 API 应用中建立清晰可维护的请求分层。
axios.create() 是什么
axios.create() 用于创建一个预配置好的 axios 实例。该实例与默认的 axios 对象共享同一套请求/响应 API,但会把你在创建时传入的配置作为该实例每一次请求的基线(baseline)。官方文档明确建议:对于任何超出单文件规模的应用,都推荐使用实例方式使用 axios。
基本用法如下:
import axios from "axios";
const instance = axios.create({
baseURL: "https://api.example.com",
timeout: 5000,
headers: { "X-Custom-Header": "foobar" },
});
create 方法接受完整的请求配置(Request Config)对象。创建之后,你可以像使用默认 axios 对象一样使用该实例:
const response = await instance.get("/users/1");
源码视角:createInstance 工厂函数
在 lib/axios.js 中,实例并非由 class 直接 new 出来的“裸对象”,而是由 createInstance 工厂函数组装而成:
function createInstance(defaultConfig) {
const context = new Axios(defaultConfig);
const instance = bind(Axios.prototype.request, context);
// Copy axios.prototype to instance
utils.extend(instance, Axios.prototype, context, { allOwnKeys: true });
// Copy context to instance
utils.extend(instance, context, null, { allOwnKeys: true });
// Factory for creating new instances
instance.create = function create(instanceConfig) {
return createInstance(mergeConfig(defaultConfig, instanceConfig));
};
return instance;
}
这段代码揭示了三个关键事实:
- 实例本身就是一个被绑定的
request函数。instance(url, config)这种“类 fetch”的直接调用方式之所以可行,是因为instance直接就是Axios.prototype.request绑定到context后的函数引用; - API 的完整继承来自两次
utils.extend。先把Axios.prototype上的所有方法(get、post、delete、put、patch、query、getUri等)复制到实例上,再把context(即Axios实例本体)复制到实例上——这就是文档所说“实例与默认对象共享同一 API”的实现来源。浏览器端测试 tests/browser/instance.browser.test.js 中的should have the same methods as default instance用例正是逐属性断言了实例与默认对象的方法类型一致性; - 实例可以递归创建。
instance.create(instanceConfig)会用mergeConfig(defaultConfig, instanceConfig)把父实例的默认配置与新配置合并后再调用createInstance。这意味着你可以在一个基础实例上派生出配置更细化的子实例,且父实例的默认值会被自动继承——这是构建“配置树”式 API 客户端的底层能力。
再看 lib/core/Axios.js 中的 Axios 类构造函数:
class Axios {
constructor(instanceConfig) {
this.defaults = instanceConfig || {};
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager(),
};
}
}
这里有两个要点:传入的配置(或空对象)被原样保存在 this.defaults 上——这就是后文“运行时修改 instance.defaults”能生效的存储位置;同时每个 Axios 上下文都持有独立的 request / response 两个 InterceptorManager——这是“拦截器隔离”这一特性的直接来源。
另外值得注意的是 lib/axios.js 中的一行:const axios = createInstance(defaults);——导出的默认 axios 对象本身也是通过同一个工厂创建的,只不过它的基线配置来自 lib/defaults/index.js 中的全局 defaults(例如 timeout: 0 表示默认不超时、validateStatus 默认只将 200~299 视为成功、headers.common 中默认 Accept: application/json, text/plain, */* 等)。因此“实例”与“默认对象”在架构上是同构的,区别仅在于 defaults 的内容。
为什么要用实例?四个典型场景
场景一:按服务划分 base URL
大多数应用会与不止一个 API 交互。为每个服务创建独立实例,可以避免在每次调用中重复书写基础 URL:
const githubApi = axios.create({ baseURL: "https://api.github.com" });
const internalApi = axios.create({ baseURL: "https://api.internal.example.com" });
const { data: repos } = await githubApi.get("/users/axios/repos");
const { data: users } = await internalApi.get("/users");
场景二:共享的认证请求头
把认证 token 挂到某个实例上,即可让该实例的每个请求自动携带 token,且不会影响其他实例:
const authApi = axios.create({
baseURL: "https://api.example.com",
headers: {
Authorization: `Bearer ${getToken()}`,
},
});
场景三:按服务区分超时(与重试策略)
不同服务的可靠性特征不同。为实时服务设置较紧的超时,为批处理任务设置较宽松的超时:
const realtimeApi = axios.create({ baseURL: "https://realtime.example.com", timeout: 2000 });
const batchApi = axios.create({ baseURL: "https://batch.example.com", timeout: 60000 });
这里有一个容易踩坑的细节:全局默认的 timeout 是 0(lib/defaults/index.js 中注释说明 “If set to 0 (default) a timeout is not created”),即不设置超时。如果你在某个实例上显式设置 timeout: 0,效果等同于取消该实例的超时保护——因此“实时服务 2 秒 / 批处理 60 秒”这类按服务差异化的配置,比在每个调用点零散传递更可控。测试用例 tests/browser/instance.browser.test.js 中的 should use instance options 验证了 axios.create({ timeout: 1000 }) 确实会把超时值作用到实际发出的请求上。
场景四:隔离的拦截器
添加到某个实例上的拦截器只对该实例生效,从而保持各模块职责分离:
const loggingApi = axios.create({ baseURL: "https://api.example.com" });
loggingApi.interceptors.request.use((config) => {
console.log(`→ ${config.method?.toUpperCase()} ${config.url}`);
return config;
});
从源码看这种隔离是如何保证的:Axios 构造函数为每个上下文创建了各自独立的 InterceptorManager(见上文 lib/core/Axios.js),而 _request 中组装拦截器链时只遍历 this.interceptors.request / this.interceptors.response(lib/core/Axios.js)——this 指向哪个实例,就只收集哪个实例注册过的拦截器。InterceptorManager(lib/core/InterceptorManager.js)内部维护自己的 handlers 数组与内部状态(use 返回递增 id、eject(id) 按 id 移除、clear() 清空全部),因此即使两个实例都在做日志、鉴权等事情,它们的拦截器栈也互不干扰。use 还支持 synchronous 与 runWhen 选项(lib/core/InterceptorManager.js),其中 runWhen(config) === false 时该拦截器会被本次请求跳过(lib/core/Axios.js),适合给实例级拦截器做条件开关。
按请求覆盖实例默认值
在请求发生时刻传入的配置,其优先级始终高于实例默认值。这一规则直接体现在 lib/core/Axios.js 的 _request 方法中:
_request(configOrUrl, config) {
// Allow for axios('example/url'[, config]) a la fetch API
if (typeof configOrUrl === 'string') {
config = config || {};
config.url = configOrUrl;
} else {
config = configOrUrl || {};
}
config = mergeConfig(this.defaults, config);
// ...
}
mergeConfig(this.defaults, config) 以实例默认值为底层、以请求级配置为上层执行合并,请求侧的显式取值会覆盖实例侧的同名值。对应文档中的示例:
const api = axios.create({ timeout: 5000 });
// This specific request uses a 30-second timeout instead
await api.get("/slow-endpoint", { timeout: 30000 });
方法别名(get、post、put、patch、delete、head、options、query 及 postForm 等 *Form 简写)在生成时也会先做一次 mergeConfig(lib/core/Axios.js),把 method、url、data 与调用方传入的配置合并后统一交给 request,最终仍汇入上面这条合并链路。相关合并行为的单元测试见 tests/unit/core/mergeConfig.test.js,其中验证了以全局 defaults 为底层合并时,返回值是新对象且 headers 是独立副本(merged.headers).not.toBe(defaults.headers))——也就是说合并不会原地修改实例默认值,请求级的修改不会“污染”实例。
另外两点与 URL 解析相关的行为也来自请求级配置优先原则:实例配置里的 url 不会覆盖调用时传入的 url(测试 tests/browser/instance.browser.test.js 验证了这一点);而 baseURL 与请求 url 的最终拼接发生在 buildFullPath(经由 dispatchRequest 调用),getUri(config) 方法(lib/core/Axios.js)也执行同样的 mergeConfig(this.defaults, config) 后再调用 buildFullPath + buildURL,可用于在不发请求的情况下预计算最终 URL(例如调试 query 序列化)。
创建之后修改 instance.defaults
文档的 tip 指出:实例默认值也可以在创建后修改,只需直接写 instance.defaults:
instance.defaults.headers.common["Authorization"] = `Bearer ${newToken}`;
这之所以可行,是因为 defaults 就是构造函数中保存的那个普通对象(this.defaults = instanceConfig || {}),而每次请求的 _request 都会实时读取 this.defaults 参与 mergeConfig,而不是在创建时做一次性快照。典型的适用场景是 token 刷新:拿到 newToken 后更新 headers.common,后续所有走该实例的请求都会自动携带新 token,无需重建实例。
结合 lib/defaults/index.js 的默认请求头结构,你可以了解到 instance.defaults.headers 的组织方式:除 common(对所有方法生效)外,还包含 delete、get、head、post、put、patch、query 各自的命名空间,用于按 HTTP 方法细分请求头。在请求管线中,这些方法专属的键会在合并后被扁平化并清理(lib/core/Axios.js 将 headers.common 与 headers[method] 合并后删除方法键),最终交由 AxiosHeaders.concat 生成规范化请求头。
小结
axios.create(config)返回一个由createInstance工厂组装的实例:被绑定的request函数 + 完整的方法集合 + 独立的defaults与interceptors(lib/axios.js、lib/core/Axios.js);- 官方推荐场景:按服务划分
baseURL、共享认证头、按服务差异化timeout、拦截器隔离; - 优先级规则:请求级配置 > 实例默认值,合并逻辑在
_request中由mergeConfig统一执行; instance.create()支持基于父实例配置派生子实例(自动mergeConfig继承);instance.defaults是实时生效的引用,可在 token 刷新等场景下原地更新。
如果想进一步掌握实例可传入的完整配置项,参见请求配置文档;拦截器的详细机制(synchronous、runWhen、移除与清理)参见拦截器文档;默认配置的完整清单见 lib/defaults/index.js。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00