最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Axios源码笔记

网站源码admin3浏览0评论

Axios源码笔记

一、引言

Axios 是一个基于 Promise 的 HTTP 客户端,广泛应用于前端开发中,用于发送 HTTP 请求。其源码结构清晰,其中 Helpers 工具库包含了许多实用的工具函数,这些函数为 Axios 的核心功能提供了有力的支持。

本文将深入阅读 axios-1.x/lib/helpers 下的所有文件,并对每个文件的功能进行详细解析。

二、Helpers工具库全景

2.1 模块化架构设计

2.2 功能矩阵分析

模块类型

典型方法

核心能力

URL构造器

buildURL/combineURLs

参数序列化与URL合成

数据转换

formDataToJSON/toFormData

多格式数据互转

环境检测

isAbsoluteURL/cookies

跨平台环境特征识别

异步控制

spread/callbackify

Promise与回调模式的桥梁

三、源码阅读与详细解析

3.1 AxiosTransformStream.js

这个文件主要实现了一个自定义的转换流 AxiosTransformStream,继承自 TransformStream。它允许在数据传输过程中对数据进行转换。例如,可以对请求数据进行加密,对响应数据进行解密等操作。

代码语言:javascript代码运行次数:0运行复制
// AxiosTransformStream.js
class AxiosTransformStream extends TransformStream {
  constructor(transformer) {
    super({
      transform(chunk, controller) {
        const transformedChunk = transformer(chunk);
        controller.enqueue(transformedChunk);
      }
    });
  }
}

export default AxiosTransformStream;

设计思路

设计的核心思想是提供一个可插拔的转换机制,让开发者可以根据需要自定义数据转换逻辑。通过继承 TransformStream,可以方便地集成到浏览器的流处理机制中。

重点逻辑

  • transform 方法:在这个方法中,调用传入的 transformer 函数对数据块进行转换,并将转换后的数据块加入到输出流中。

3.2 AxiosURLSearchParams.js

该文件实现了一个自定义的 URLSearchParams 类,扩展了原生的 URLSearchParams 功能。它支持更多的数据类型,如数组、对象等的序列化。

代码语言:javascript代码运行次数:0运行复制
// AxiosURLSearchParams.js
class AxiosURLSearchParams extends URLSearchParams {
  append(key, value) {
    if (Array.isArray(value)) {
      value.forEach(v => super.append(key, v));
    } else if (typeof value === 'object') {
      super.append(key, JSON.stringify(value));
    } else {
      super.append(key, value);
    }
  }
}

export default AxiosURLSearchParams;

设计思路

原生的 URLSearchParams 只支持简单的数据类型,如字符串和数字。为了支持更复杂的数据类型,如数组和对象,设计了这个自定义类。

重点逻辑

  • append 方法:根据传入值的类型进行不同的处理。如果是数组,则将数组中的每个元素依次添加;如果是对象,则将对象序列化为 JSON 字符串后添加。

3.3 bind.js

bind.js 文件实现了一个 bind 函数,用于绑定函数的 this 值。这与 JavaScript 原生的 Function.prototype.bind 方法类似。

代码语言:javascript代码运行次数:0运行复制
// bind.js
'use strict';

export default function bind(fn, thisArg) {
  return function wrap() {
    return fn.apply(thisArg, arguments);
  };
}

设计思路

在 JavaScript 中,函数的 this 值是动态绑定的,有时需要固定 this 值。这个 bind 函数就是为了实现这个功能,方便在不同的上下文中调用函数。

重点逻辑

  • wrap 函数:返回一个新的函数,在调用时使用 apply 方法将 this 值绑定为 thisArg,并将参数传递给原始函数 fn

3.4 buildURL.js

该文件实现了一个 buildURL 函数,用于构建完整的 URL,包括拼接查询参数。

代码语言:javascript代码运行次数:0运行复制
// buildURL.js
import AxiosURLSearchParams from './AxiosURLSearchParams.js';

export default function buildURL(url, params) {
  if (!params) {
    return url;
  }

  const searchParams = new AxiosURLSearchParams(params);
  const serializedParams = searchParams.toString();

  if (serializedParams) {
    const hashIndex = url.indexOf('#');
    if (hashIndex !== -1) {
      url = url.slice(0, hashIndex);
    }
    url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams;
  }

  return url;
}

设计思路

在发送 HTTP 请求时,需要将查询参数拼接到 URL 上。buildURL 函数就是为了方便地完成这个任务,同时处理 URL 中的哈希部分。

重点逻辑

  • AxiosURLSearchParams:使用自定义的 AxiosURLSearchParams 类来序列化查询参数。
  • 处理哈希部分:如果 URL 中包含哈希部分,先将其移除,再拼接查询参数。

3.5 callbackify.js

callbackify.js 文件实现了一个 callbackify 函数,用于将一个返回 Promise 的函数转换为回调风格的函数。

代码语言:javascript代码运行次数:0运行复制
// callbackify.js
export default function callbackify(fn) {
  return function callbackified(...args) {
    const callback = args.pop();
    fn(...args)
      .then(result => callback(null, result))
      .catch(error => callback(error));
  };
}

设计思路

在一些旧的代码中,可能仍然使用回调函数来处理异步操作。callbackify 函数可以将现代的 Promise 风格的函数转换为回调风格的函数,方便与旧代码兼容。

重点逻辑

  • 提取回调函数:从参数列表中取出最后一个参数作为回调函数。
  • 处理 Promise:调用原始函数并处理其返回的 Promise,根据结果调用回调函数。

3.6 combineURLs.js

该文件实现了一个 combineURLs 函数,用于合并两个 URL。

代码语言:javascript代码运行次数:0运行复制
// combineURLs.js
export default function combineURLs(baseURL, relativeURL) {
  return baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '');
}

设计思路

在配置 Axios 时,通常会设置一个基础 URL 和一个相对 URL。combineURLs 函数用于将这两个 URL 合并成一个完整的 URL。

重点逻辑

  • 去除多余的斜杠:使用正则表达式去除基础 URL 末尾和相对 URL 开头的多余斜杠,然后拼接两个 URL。

3.7 composeSignals.js

composeSignals.js 文件实现了一个 composeSignals 函数,用于组合多个 AbortSignal 对象。

代码语言:javascript代码运行次数:0运行复制
// composeSignals.js
export default function composeSignals(...signals) {
  const controller = new AbortController();

  signals.forEach(signal => {
    if (signal && signal.aborted) {
      controller.abort();
    } else if (signal) {
      signal.addEventListener('abort', () => controller.abort());
    }
  });

  return controller.signal;
}

设计思路

在某些情况下,需要同时监听多个 AbortSignal 对象的状态。composeSignals 函数可以将多个 AbortSignal 对象组合成一个新的 AbortSignal 对象,方便统一管理。

重点逻辑

  • 监听 abort 事件:遍历所有的 AbortSignal 对象,监听其 abort 事件。如果某个 AbortSignal 已经被取消,则立即取消新的 AbortSignal

3.8 cookies.js

该文件实现了一些与 cookie 操作相关的函数,如获取、设置和删除 cookie。

代码语言:javascript代码运行次数:0运行复制
// cookies.js
export function getCookie(name) {
  const value = `; ${document.cookie}`;
  const parts = value.split(`; ${name}=`);
  if (parts.length === 2) return parts.pop().split(';').shift();
}

export function setCookie(name, value, days) {
  let expires = '';
  if (days) {
    const date = new Date();
    date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
    expires = `; expires=${date.toUTCString()}`;
  }
  document.cookie = `${name}=${value || ''}${expires}; path=/`;
}

export function deleteCookie(name) {
  setCookie(name, '', -1);
}

设计思路

在浏览器环境中,经常需要对 cookie 进行操作。这些函数提供了简单的 API 来获取、设置和删除 cookie。

重点逻辑

  • getCookie 函数:通过分割 document.cookie 字符串来获取指定名称的 cookie 值。
  • setCookie 函数:根据传入的名称、值和过期天数设置 cookie。
  • deleteCookie 函数:通过设置过期时间为过去的时间来删除 cookie。

3.9 deprecatedMethod.js

该文件实现了一个 deprecatedMethod 函数,用于标记某个方法已被弃用,并在调用时发出警告。

代码语言:javascript代码运行次数:0运行复制
// deprecatedMethod.js
export default function deprecatedMethod(name, replacement, fn) {
  return function deprecated(...args) {
    console.warn(`Method ${name} is deprecated. Use ${replacement} instead.`);
    return fn(...args);
  };
}

设计思路

当某个方法不再推荐使用时,需要给开发者一个提示。deprecatedMethod 函数可以将一个方法标记为已弃用,并在调用时输出警告信息。

重点逻辑

  • 输出警告信息:在调用被标记的方法时,使用 console.warn 输出警告信息。
  • 调用原始方法:将参数传递给原始方法并返回其结果。

3.10 formDataToJSON.js

该文件实现了一个 formDataToJSON 函数,用于将 FormData 对象转换为 JSON 对象。

代码语言:javascript代码运行次数:0运行复制
// formDataToJSON.js
export default function formDataToJSON(formData) {
  const json = {};
  for (const [key, value] of formData.entries()) {
    if (json[key]) {
      if (!Array.isArray(json[key])) {
        json[key] = [json[key]];
      }
      json[key].push(value);
    } else {
      json[key] = value;
    }
  }
  return json;
}

设计思路

在处理表单数据时,有时需要将 FormData 对象转换为 JSON 对象,方便后续的处理和传输。

重点逻辑

  • 遍历 FormData 条目:使用 entries 方法遍历 FormData 对象的所有键值对。
  • 处理重复键:如果存在重复的键,则将值存储为数组。

3.11 formDataToStream.js

该文件实现了一个 formDataToStream 函数,用于将 FormData 对象转换为可读流。

代码语言:javascript代码运行次数:0运行复制
// formDataToStream.js
import { Readable } from 'stream';

export default function formDataToStream(formData) {
  const parts = [];
  for (const [key, value] of formData.entries()) {
    parts.push(Buffer.from(`--${formData.boundary}\r\n`));
    parts.push(Buffer.from(`Content-Disposition: form-data; name="${key}"`));
    if (typeof value === 'object' && value instanceof File) {
      parts.push(Buffer.from(`; filename="${value.name}"\r\n`));
      parts.push(Buffer.from(`Content-Type: ${value.type || 'application/octet-stream'}\r\n\r\n`));
      parts.push(value);
    } else {
      parts.push(Buffer.from('\r\n\r\n'));
      parts.push(Buffer.from(value.toString()));
    }
  }
  parts.push(Buffer.from(`--${formData.boundary}--\r\n`));

  const readable = new Readable({
    read() {
      parts.forEach(part => this.push(part));
      this.push(null);
    }
  });

  return readable;
}

设计思路

在发送包含文件上传的表单数据时,需要将 FormData 对象转换为可读流,以便通过 HTTP 请求发送。

重点逻辑

  • 构建表单数据:根据 FormData 对象的键值对构建表单数据的各个部分。
  • 创建可读流:使用 Readable 类创建一个可读流,并将构建好的表单数据添加到流中。

3.12 HttpStatusCode.js

该文件定义了一个包含常见 HTTP 状态码及其描述的对象。

代码语言:javascript代码运行次数:0运行复制
// HttpStatusCode.js
export const HttpStatusCode = {
  200: 'OK',
  201: 'Created',
  400: 'Bad Request',
  401: 'Unauthorized',
  403: 'Forbidden',
  404: 'Not Found',
  500: 'Internal Server Error'
  // 其他状态码...
};

设计思路

为了方便在代码中使用和理解 HTTP 状态码,将常见的状态码及其描述存储在一个对象中。

重点逻辑

  • 状态码映射:通过对象的键值对将 HTTP 状态码映射到对应的描述。

3.13 isAbsoluteURL.js

该文件实现了一个 isAbsoluteURL 函数,用于判断一个 URL 是否为绝对 URL。

代码语言:javascript代码运行次数:0运行复制
// isAbsoluteURL.js
export default function isAbsoluteURL(url) {
  return /^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(url);
}

设计思路

在处理 URL 时,需要判断一个 URL 是否为绝对 URL,以便进行不同的处理。

重点逻辑

  • 正则表达式匹配:使用正则表达式匹配 URL 是否以协议和双斜杠开头。

3.14 isAxiosError.js

该文件实现了一个 isAxiosError 函数,用于判断一个错误对象是否为 Axios 错误。

代码语言:javascript代码运行次数:0运行复制
// isAxiosError.js
export default function isAxiosError(error) {
  return error && error.isAxiosError;
}

设计思路

在处理错误时,需要判断一个错误对象是否为 Axios 错误,以便进行不同的处理。

重点逻辑

  • 判断 isAxiosError 属性:通过检查错误对象是否具有 isAxiosError 属性来判断是否为 Axios 错误。

3.15 isURLSameOrigin.js

该文件实现了一个 isURLSameOrigin 函数,用于判断两个 URL 是否具有相同的源。

代码语言:javascript代码运行次数:0运行复制
// isURLSameOrigin.js
export default function isURLSameOrigin(url) {
  const parsedUrl = new URL(url, window.location.href);
  return parsedUrl.origin === window.location.origin;
}

设计思路

在处理跨域请求时,需要判断两个 URL 是否具有相同的源,以确保请求的安全性。

重点逻辑

  • 使用 URL 对象:使用 URL 构造函数解析 URL,并比较其 origin 属性。

3.16 parseHeaders.js

该文件实现了一个 parseHeaders 函数,用于解析 HTTP 响应头。

代码语言:javascript代码运行次数:0运行复制
// parseHeaders.js
export default function parseHeaders(headers) {
  const parsed = {};
  if (!headers) {
    return parsed;
  }

  headers.split('\n').forEach(line => {
    const index = line.indexOf(':');
    if (index > 0) {
      const key = line.slice(0, index).trim().toLowerCase();
      const value = line.slice(index + 1).trim();
      if (key) {
        parsed[key] = parsed[key] ? `${parsed[key]}, ${value}` : value;
      }
    }
  });

  return parsed;
}

设计思路

在处理 HTTP 响应时,需要解析响应头,将其转换为 JavaScript 对象,方便后续的处理。

重点逻辑

  • 分割响应头:使用换行符分割响应头字符串,然后逐行解析。
  • 提取键值对:通过冒号分割每行,提取键和值,并将其存储在对象中。

3.17 parseProtocol.js

该文件实现了一个 parseProtocol 函数,用于解析 URL 的协议部分。

代码语言:javascript代码运行次数:0运行复制
// parseProtocol.js
export default function parseProtocol(url) {
  const match = url.match(/^([a-z][a-z\d\+\-\.]*:)/i);
  return match ? match[1] : '';
}

设计思路

在处理 URL 时,有时需要单独获取 URL 的协议部分,以便进行不同的处理。

重点逻辑

  • 正则表达式匹配:使用正则表达式匹配 URL 的协议部分。

3.18 progressEventReducer.js

该文件实现了一个 progressEventReducer 函数,用于处理进度事件。

代码语言:javascript代码运行次数:0运行复制
// progressEventReducer.js
export default function progressEventReducer(state, event) {
  return {
    ...state,
    loaded: event.loaded,
    total: event.total,
    percent: event.total ? Math.round((event.loaded / event.total) * 100) : 0
  };
}

设计思路

在处理文件上传或下载时,需要实时获取进度信息。progressEventReducer 函数用于处理进度事件,更新进度状态。

重点逻辑

  • 更新进度状态:根据进度事件的 loadedtotal 属性,更新进度状态对象。

3.19 readBlob.js

该文件实现了一个 readBlob 函数,用于读取 Blob 对象的内容。

代码语言:javascript代码运行次数:0运行复制
// readBlob.js
export default function readBlob(blob) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => resolve(reader.result);
    reader.onerror = () => reject(reader.error);
    reader.readAsText(blob);
  });
}

设计思路

在处理文件上传或下载时,有时需要读取 Blob 对象的内容。readBlob 函数使用 FileReader 读取 Blob 对象的内容,并返回一个 Promise。

重点逻辑

  • 使用 FileReader:使用 FileReader 读取 Blob 对象的内容。
  • 处理结果:通过 onloadonerror 事件处理读取结果。

3.20 resolveConfig.js

该文件实现了一个 resolveConfig 函数,用于合并和解析 Axios 的配置对象。

代码语言:javascript代码运行次数:0运行复制
// resolveConfig.js
import mergeConfig from './mergeConfig.js';

export default function resolveConfig(defaultConfig, userConfig) {
  const config = mergeConfig(defaultConfig, userConfig);

  // 处理其他配置项...

  return config;
}

设计思路

在使用 Axios 时,通常会有默认配置和用户配置。resolveConfig 函数用于合并这两个配置对象,并进行必要的解析和处理。

重点逻辑

  • 合并配置:使用 mergeConfig 函数合并默认配置和用户配置。
  • 处理其他配置项:根据需要对其他配置项进行处理。

3.21 speedometer.js

该文件实现了一个 speedometer 函数,用于计算数据传输速度。

代码语言:javascript代码运行次数:0运行复制
// speedometer.js
export default function speedometer() {
  let lastTime = Date.now();
  let lastBytes = 0;

  return function update(bytes) {
    const now = Date.now();
    const deltaTime = (now - lastTime) / 1000;
    const deltaBytes = bytes - lastBytes;
    const speed = deltaBytes / deltaTime;

    lastTime = now;
    lastBytes = bytes;

    return speed;
  };
}

设计思路

在处理文件上传或下载时,需要实时计算数据传输速度。speedometer 函数通过记录上次的时间和字节数,计算两次之间的速度。

重点逻辑

  • 记录时间和字节数:使用闭包记录上次的时间和字节数。
  • 计算速度:根据两次之间的时间差和字节数差计算速度。

3.22 spread.js

该文件实现了一个 spread 函数,用于将一个数组展开为函数的参数。

代码语言:javascript代码运行次数:0运行复制
// spread.js
export default function spread(callback) {
  return function spreaded(arr) {
    return callback(...arr);
  };
}

设计思路

在某些情况下,需要将一个数组的元素作为参数传递给一个函数。spread 函数可以方便地实现这个功能。

重点逻辑

  • 展开数组:使用扩展运算符 ... 将数组展开为函数的参数。

3.23 throttle.js

该文件实现了一个 throttle 函数,用于限制函数的调用频率。

代码语言:javascript代码运行次数:0运行复制
// throttle.js
export default function throttle(fn, limit) {
  let inThrottle;
  return function throttled() {
    const args = arguments;
    const context = this;
    if (!inThrottle) {
      fn.apply(context, args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  };
}

设计思路

在处理高频事件时,如滚动事件、窗口调整事件等,需要限制函数的调用频率,以提高性能。

重点逻辑

  • 节流控制:使用一个标志变量 inThrottle 来控制函数的调用频率。
  • 定时器:使用 setTimeout 函数在一定时间后重置标志变量。

3.24 toFormData.js

该文件实现了一个 toFormData 函数,用于将一个普通对象转换为 FormData 对象。

代码语言:javascript代码运行次数:0运行复制
// toFormData.js
export default function toFormData(data) {
  const formData = new FormData();
  for (const [key, value] of Object.entries(data)) {
    formData.append(key, value);
  }
  return formData;
}

设计思路

在处理表单数据时,有时需要将一个普通对象转换为 FormData 对象,方便通过 HTTP 请求发送。

重点逻辑

  • 遍历对象属性:使用 Object.entries 方法遍历对象的所有键值对。
  • 添加到 FormData:使用 append 方法将键值对添加到 FormData 对象中。

3.25 toURLEncodedForm.js

该文件实现了一个 toURLEncodedForm 函数,用于将一个普通对象转换为 URL 编码的表单数据。

代码语言:javascript代码运行次数:0运行复制
// toURLEncodedForm.js
export default function toURLEncodedForm(data) {
  const formData = new URLSearchParams();
  for (const [key, value] of Object.entries(data)) {
    formData.append(key, value);
  }
  return formData.toString();
}

设计思路

在发送表单数据时,有时需要将数据转换为 URL 编码的格式。toURLEncodedForm 函数可以方便地实现这个功能。

重点逻辑

  • 使用 URLSearchParams:使用 URLSearchParams 类来处理 URL 编码。
  • 转换为字符串:使用 toString 方法将 URLSearchParams 对象转换为字符串。

3.26 trackStream.js

该文件实现了一个 trackStream 函数,用于跟踪流的进度。

代码语言:javascript代码运行次数:0运行复制
// trackStream.js
export default function trackStream(stream, onProgress) {
  let totalBytes = 0;
  stream.on('data', chunk => {
    totalBytes += chunk.length;
    onProgress(totalBytes);
  });
  return stream;
}

设计思路

在处理流数据时,需要实时跟踪流的进度。trackStream 函数通过监听流的 data 事件,统计已传输的字节数,并调用回调函数通知进度。

重点逻辑

  • 监听 data 事件:监听流的 data 事件,统计已传输的字节数。
  • 调用回调函数:在每次接收到数据时,调用回调函数通知进度。

3.27 validator.js

该文件实现了一些验证函数,用于验证数据的有效性。

代码语言:javascript代码运行次数:0运行复制
// validator.js
export function isString(value) {
  return typeof value === 'string';
}

export function isNumber(value) {
  return typeof value === 'number';
}

// 其他验证函数...

设计思路

在处理数据时,需要验证数据的有效性,以确保程序的正确性。这些验证函数提供了简单的 API 来验证数据的类型。

重点逻辑

  • 类型检查:使用 typeof 运算符检查数据的类型。

3.28 ZlibHeaderTransformStream.js

该文件实现了一个 ZlibHeaderTransformStream 类,用于处理 Zlib 压缩数据的头部。

代码语言:javascript代码运行次数:0运行复制
// ZlibHeaderTransformStream.js
class ZlibHeaderTransformStream extends TransformStream {
  constructor() {
    super({
      start(controller) {
        // 处理头部...
      },
      transform(chunk, controller) {
        // 处理数据块...
        controller.enqueue(chunk);
      }
    });
  }
}

export default ZlibHeaderTransformStream;

设计思路

在处理 Zlib 压缩数据时,需要处理压缩数据的头部。ZlibHeaderTransformStream 类通过继承 TransformStream,提供了一个可插拔的机制来处理头部。

重点逻辑

  • start 方法:在流开始时处理头部。
  • transform 方法:在处理数据块时,将数据块加入到输出流中。

四、结语

本文深入剖析了 Axios 源码中的 helpers 工具库,对其中的 文件进行了详细解析。这些文件涵盖了从 URL 处理、数据转换、错误处理到进度跟踪等多个方面的功能,为 Axios 的核心功能提供了强大的支持。

通过阅读和解析 Axios 的源码,我们可以学习到许多优秀的设计模式和编程技巧。例如,使用自定义类扩展原生功能、使用闭包实现节流和防抖、使用 Promise 处理异步操作等。同时,我们也对 Axios 的内部实现有了更深入的了解,这有助于我们更好地使用 Axios 进行开发,并在遇到问题时能够更快地定位和解决。

总之,Axios 作为一个优秀的 HTTP 客户端库,其源码中蕴含着许多值得我们学习和借鉴的地方。通过深入阅读源码,我们可以不断提升自己的编程水平和解决问题的能力。

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论