axios 源码笔记(一)

目录结构

在这里插入图片描述

  • 源码在lib中(axios版本v0.20)
  • adapters 封装了node和browser请求
  • cancel 取消请求
  • core 核心代码
  • help 拆分的方法
  • axios 入口函数,返回了axios一个实例
  • defaults 默认配置
  • utils 工具函数

入口

从package入手,main标识了入口为index.js。
index抛出了axios.js
axios.js返回了一个axios实例,这个实例实质上是一个方法

lib/axios.js

var utils = require('./utils');
var bind = require('./helpers/bind');
var Axios = require('./core/Axios');
var mergeConfig = require('./core/mergeConfig');
var defaults = require('./defaults');

/**
 * Create an instance of Axios
 *
 * @param {Object} defaultConfig The default config for the instance
 * @return {Axios} A new instance of Axios
 */
function createInstance(defaultConfig) {
  var context = new Axios(defaultConfig);
  // 返回了Axios的实例,instance为返回的实例,将context实例的方法附给了instance,为了直接调用request(Axios())
  var instance = bind(Axios.prototype.request, context);

  // Copy axios.prototype to instance
  utils.extend(instance, Axios.prototype, context);

  // Copy context to instance
  utils.extend(instance, context);

  return instance;
}

// Create the default instance to be exported
var axios = createInstance(defaults);

// Expose Axios class to allow class inheritance
axios.Axios = Axios;

// Factory for creating new instances
axios.create = function create(instanceConfig) {
  return createInstance(mergeConfig(axios.defaults, instanceConfig));
};

// Expose Cancel & CancelToken
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');

// Expose all/spread
axios.all = function all(promises) {
  return Promise.all(promises);
};
axios.spread = require('./helpers/spread');

module.exports = axios;

// 允许使用Ts 中的 default import 语法
module.exports.default = axios;
  1. 直接抛出axios,并且抛出default,为了兼容es6 import defaultModule from ‘axios’
  2. axios 是createInstance返回的一个实例
  3. createInstance方法接受config参数,首先new 一个Axios实例context;其次将request方法绑定绑定到context上,并赋值给instance,这样实例instance是对象也是一个方法,可以直接调用Axios();最后instance继承Axios.prototype 和 context方法属性并返回instance
  4. 给axios添加create方法,这是个工厂函数,合并了传入的属性和默认属性。并通过createInstance返回实例
  5. 依次给axios添加Cancel、CancelToken、isCancel、all、spread等方法
  6. all方法即为promise.all

lib/default

'use strict';

var utils = require('./utils');
var normalizeHeaderName = require('./helpers/normalizeHeaderName');

var DEFAULT_CONTENT_TYPE = {
  'Content-Type': 'application/x-www-form-urlencoded'
};

function setContentTypeIfUnset(headers, value) {
  if (!utils.isUndefined(headers) && utils.isUndefined(headers['Content-Type'])) {
    headers['Content-Type'] = value;
  }
}

function getDefaultAdapter() {
  var adapter;
  // 如果XMLHttpRequest存在说明是在浏览器环境,此时加载xhr
  if (typeof XMLHttpRequest !== 'undefined') {
    // For browsers use XHR adapter
    adapter = require('./adapters/xhr');
  } else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
    // For node use HTTP adapter
    adapter = require('./adapters/http');
  }
  return adapter;
}

var defaults = {
  adapter: getDefaultAdapter(),

  transformRequest: [function transformRequest(data, headers) {
    normalizeHeaderName(headers, 'Accept');
    normalizeHeaderName(headers, 'Content-Type');
    if (utils.isFormData(data) ||
      utils.isArrayBuffer(data) ||
      utils.isBuffer(data) ||
      utils.isStream(data) ||
      utils.isFile(data) ||
      utils.isBlob(data)
    ) {
      return data;
    }
    if (utils.isArrayBufferView(data)) {
      return data.buffer;
    }
    if (utils.isURLSearchParams(data)) {
      setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8');
      return data.toString();
    }
    if (utils.isObject(data)) {
      setContentTypeIfUnset(headers, 'application/json;charset=utf-8');
      return JSON.stringify(data);
    }
    return data;
  }],

  transformResponse: [function transformResponse(data) {
    /*eslint no-param-reassign:0*/
    if (typeof data === 'string') {
      try {
        data = JSON.parse(data);
      } catch (e) { /* Ignore */ }
    }
    return data;
  }],

  /**
   * A timeout in milliseconds to abort a request. If set to 0 (default) a
   * timeout is not created.
   */
  timeout: 0,
  // 防止xsrf攻击,存储xsrfCookie的名字
  xsrfCookieName: 'XSRF-TOKEN',
  // `xsrfHeaderName` is the name of the http header that carries the xsrf token value
  xsrfHeaderName: 'X-XSRF-TOKEN',
  // 定义允许的响应内容的最大尺寸
  maxContentLength: -1,
  
  maxBodyLength: -1,
  // 定义对于给定的HTTP 响应状态码是 resolve 或 reject  promise 。如果 `validateStatus` 返回 `true` (或者设置为 `null` 或 `undefined`),promise 将被 resolve; 否则,promise 将被 rejecte
  validateStatus: function validateStatus(status) {
    return status >= 200 && status < 300;
  }
};

defaults.headers = {
  common: {
    'Accept': 'application/json, text/plain, */*'
  }
};
// 对于不需要请求参数的方法,设置header为空对象
utils.forEach(['delete', 'get', 'head'], function forEachMethodNoData(method) {
  defaults.headers[method] = {};
});

utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  defaults.headers[method] = utils.merge(DEFAULT_CONTENT_TYPE);
});

module.exports = defaults;

  1. 作用:axios.js createInstance时传入的默认配置
  2. adapter:主要用来处理请求,分为node和browser两个环境
  3. transformRequest:是为了在请求发出去之前,修改请求参数,只支持’PUT’, ‘POST’ 和 ‘PATCH’,将请求头中参数统一大小写;如果请求参数是可以直接解析,直接返回,否则根据不同的格式进行解析
  4. 给不同的请求方法设置默认的content-type,如果不需要参数,则是这为空,最终的样子
  headers: 
   { common: { Accept: 'application/json, text/plain, */*' },
     delete: {},
     get: {},
     head: {},
     post: { 'Content-Type': 'application/x-www-form-urlencoded' },
     put: { 'Content-Type': 'application/x-www-form-urlencoded' },
     patch: { 'Content-Type': 'application/x-www-form-urlencoded' } } }

lib/utils

检测函数

// 方便下文调用Object的方法,用于检测对象的类型
// toString() 方法返回一个表示该对象的字符串
var toString = Object.prototype.toString;
// 是否为数组
function isArray(val) {
  return toString.call(val) === '[object Array]';
}
// 是否为undefined
function isUndefined(val) {
  return typeof val === 'undefined';
}

// 是否为buffer
function isBuffer(val) {
  return val !== null && !isUndefined(val) && val.constructor !== null && !isUndefined(val.constructor)
    && typeof val.constructor.isBuffer === 'function' && val.constructor.isBuffer(val);
}
// 是否为buffer数组
function isArrayBuffer(val) {
  return toString.call(val) === '[object ArrayBuffer]';
}
// instanceof 用来判断val是否为FormData实例,typeof返回操作数的基础类型
function isFormData(val) {
  return (typeof FormData !== 'undefined') && (val instanceof FormData);
}
// 判断是否为arrayBuffer视图,ArrayBufferView不是对象,也不是函数接口,是一类辅助描述数组的程序,表示一类的二进制数组(Int8Array,Uint8Array等)
function isArrayBufferView(val) {
  var result;
  if ((typeof ArrayBuffer !== 'undefined') && (ArrayBuffer.isView)) {
    result = ArrayBuffer.isView(val);
  } else {
    result = (val) && (val.buffer) && (val.buffer instanceof ArrayBuffer);
  }
  return result;
}

function isString(val) {
  return typeof val === 'string';
}

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

function isObject(val) {
  return val !== null && typeof val === 'object';
}
// 是否为纯粹的对象,通过Object.create(),new Object(),{}创建的为纯粹的对象
// Object.create()默认传入Object.prototype,因此可以通过Object.create(null)创建干净的对象
function isPlainObject(val) {
  if (toString.call(val) !== '[object Object]') {
    return false;
  }

  var prototype = Object.getPrototypeOf(val);
  return prototype === null || prototype === Object.prototype;
}

function isDate(val) {
  return toString.call(val) === '[object Date]';
}

function isFile(val) {
  return toString.call(val) === '[object File]';
}

function isBlob(val) {
  return toString.call(val) === '[object Blob]';
}

function isFunction(val) {
  return toString.call(val) === '[object Function]';
}

function isStream(val) {
  return isObject(val) && isFunction(val.pipe);
}
// 是否为URLSearchParams
// URLSearchParams接口定义了一些实用的方法来处理url的查询字符串
// URLSearchParams构造函数不会解析完整 URL,但是如果字符串起始位置有?的话会被去除。
function isURLSearchParams(val) {
  return typeof URLSearchParams !== 'undefined' && val instanceof URLSearchParams;
}

isStandardBrowserEnv 方法

function isStandardBrowserEnv() {
  if (typeof navigator !== 'undefined' && (navigator.product === 'ReactNative' ||
                                           navigator.product === 'NativeScript' ||
                                           navigator.product === 'NS')) {
    return false;
  }
  return (
    typeof window !== 'undefined' &&
    typeof document !== 'undefined'
  );
}

判断是否为标准的浏览器,webwork和reactNative,支持XMLHttpRequest,但是不完全支持全局api

forEach 方法

function forEach(obj, fn) {
  // Don't bother if no value provided
  if (obj === null || typeof obj === 'undefined') {
    return;
  }

  // 如果不是对象,强制转化为数组进行迭代
  if (typeof obj !== 'object') {
    /*eslint no-param-reassign:0*/
    obj = [obj];
  }
  // 遍历数组和对象。将key,index,原对象作为参数执行传入的fn
  if (isArray(obj)) {
    for (var i = 0, l = obj.length; i < l; i++) {
      fn.call(null, obj[i], i, obj);
    }
  } else {
    // Iterate over object keys
    for (var key in obj) {
      // 返回boolean,使用.call,使用对象上的方法,防止hasOwnProperty方法被占用
      if (Object.prototype.hasOwnProperty.call(obj, key)) {
        fn.call(null, obj[key], key, obj);
      }
    }
  }
}

extend 方法


// a为返回的对象,b为需要遍历的对象,thisArg上下文
function extend(a, b, thisArg) {
  // 若b为object,取出key和val;若b为数组,取出val和index,并带入assignValue执行
  forEach(b, function assignValue(val, key) {
	// 如果上下文存在并且val为fun,a[key]赋值函数val并传入上下文
    if (thisArg && typeof val === 'function') {
      a[key] = bind(val, thisArg);
    } else {
      a[key] = val;
    }
  });
  return a;
}

merge 方法

function merge(/* obj1, obj2, obj3, ... */) {
  var result = {};
  function assignValue(val, key) {
    if (isPlainObject(result[key]) && isPlainObject(val)) {
      // 如果result中已经包含key,则合并result[key]和val
      result[key] = merge(result[key], val);
    } else if (isPlainObject(val)) {
      // 如果val为对象,迭代merge
      result[key] = merge({}, val);
    } else if (isArray(val)) {
      // 如果val为数组,slice相当于复制了一下数组
      result[key] = val.slice();
    } else {
      result[key] = val;
    }
  }

  for (var i = 0, l = arguments.length; i < l; i++) {
    // 传入的每个obj都执行assignValue方法,合并成一个obj
    forEach(arguments[i], assignValue);
  }
  return result;
}

stripBOM 方法

function stripBOM(content) {
  if (content.charCodeAt(0) === 0xFEFF) {
    content = content.slice(1);
  }
  return content;
}

去除BOM标记,BOM是用来判断文本文件是哪一种Unicode编码的标记,其本身是一个Unicode字符,位于文本文件头部,用来标识字节序 (Big/Little Endian),除此以外还可以标识编码(UTF-8/16/32),如果出现在文本中间,则解释为zero width no-break space

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值