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;
- 直接抛出axios,并且抛出default,为了兼容es6 import defaultModule from ‘axios’
- axios 是createInstance返回的一个实例
- createInstance方法接受config参数,首先new 一个Axios实例context;其次将request方法绑定绑定到context上,并赋值给instance,这样实例instance是对象也是一个方法,可以直接调用Axios();最后instance继承Axios.prototype 和 context方法属性并返回instance
- 给axios添加create方法,这是个工厂函数,合并了传入的属性和默认属性。并通过createInstance返回实例
- 依次给axios添加Cancel、CancelToken、isCancel、all、spread等方法
- 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;
- 作用:axios.js createInstance时传入的默认配置
- adapter:主要用来处理请求,分为node和browser两个环境
- transformRequest:是为了在请求发出去之前,修改请求参数,只支持’PUT’, ‘POST’ 和 ‘PATCH’,将请求头中参数统一大小写;如果请求参数是可以直接解析,直接返回,否则根据不同的格式进行解析
- 给不同的请求方法设置默认的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