很多框架都涉及到一个configMerge的部分。本文主要分析axios的configMerge部分源代码,主要回答两个问题:axios如何进行configMerge以及它为什么这么做。文章最后会给出相关部分的源代码。
一、预备概念
在源码解读之前,我们先进行一些准备工作。首先我们将config分为两种:UserConfig、DefaultConfig。UserConfig
是创建实例处理化传递的根据应用场景而变化的参数,DefaultConfig
是在框架编写时默认的参数。而configMerge的目标就是合并两种config。
二、源码解读
2.1 属性分类
首先axios将它用到的所有config属性分为三类:
- 没有初始值,其值必须由初始化的时候指定
- 需要合并的属性,处理时需要将Object对象合并
- 普通属性,处理时如果UserConfig中有则以UserConfig为准,否则取DefaultConfig的值。这些属性其值的类型除了
number
、string
甚至可以为function
、array
等复杂类型。
var valueFromConfig2Keys = ['url', 'method', 'params', 'data'];
var mergeDeepPropertiesKeys = ['headers', 'auth', 'proxy'];
var defaultToConfig2Keys = [
'baseURL', 'url', 'transformRequest', 'transformResponse', 'paramsSerializer',
'timeout', 'withCredentials', 'adapter', 'responseType', 'xsrfCookieName',
'xsrfHeaderName', 'onUploadProgress', 'onDownloadProgress',
'maxContentLength', 'validateStatus', 'maxRedirects', 'httpAgent',
'httpsAgent', 'cancelToken', 'socketPath'
];
2.2 分别处理
对于第一类先判断在UserConfig里面属性对应的值是否存在,如果存在就拿出来。
utils.forEach(valueFromConfig2Keys, function valueFromConfig2(prop) {
if (typeof config2[prop] !== 'undefined') {
config[prop] = config2[prop];
}
});
对于第二类属性都是需要合并的object类型,因此使用专门的函数 utils.deepMerge来合并两个object
utils.forEach(mergeDeepPropertiesKeys, function mergeDeepProperties(prop) {
if (utils.isObject(config2[prop])) {
config[prop] = utils.deepMerge(config1[prop], config2[prop]);
} else if (typeof config2[prop] !== 'undefined') {
config[prop] = config2[prop];
} else if (utils.isObject(config1[prop])) {
config[prop] = utils.deepMerge(config1[prop]);
} else if (typeof config1[prop] !== 'undefined') {
config[prop] = config1[prop];
}
});
对于第三类,它的处理逻辑和第二类一样。如果userConfig里面存在那么以userConfig为准,否则取默认值,只不过此时是值类型,不需要前面那么麻烦。代码如下:
utils.forEach(defaultToConfig2Keys, function defaultToConfig2(prop) {
if (typeof config2[prop] !== 'undefined') {
config[prop] = config2[prop];
} else if (typeof config1[prop] !== 'undefined') {
config[prop] = config1[prop];
}
});
2.3 剩余属性补全
获取userconfig中存在但是还没有被添加到config中的属性,一一将其添加到config。
var axiosKeys = valueFromConfig2Keys
.concat(mergeDeepPropertiesKeys)
.concat(defaultToConfig2Keys);
var otherKeys = Object
.keys(config2)
.filter(function filterAxiosKeys(key) {
return axiosKeys.indexOf(key) === -1;
});
utils.forEach(otherKeys, function otherKeysDefaultToConfig2(prop) {
if (typeof config2[prop] !== 'undefined') {
config[prop] = config2[prop];
} else if (typeof config1[prop] !== 'undefined') {
config[prop] = config1[prop];
}
});
三、问题
- valueFromConfig2Keys 中的属性不需要单独分出来,但是这些属性具有一些特点,UserConfig中才有,因此将其单独拿出来代码更容易理解。如果将其并入defaultToConfig2Keys中也是可以的,这样仅会缩减5行代码。
- 这个合并过程看起来很冗余,在userconfig的基础上对其进行扩展那不是更方便?但是要求UserConfig和DefaultConfig都不变就只能这样做了,这是axios的应用场景导致的。
四、完整源码
function mergeConfig(config1, config2) {
// eslint-disable-next-line no-param-reassign
config2 = config2 || {};
var config = {};
var valueFromConfig2Keys = ['url', 'method', 'params', 'data'];
var mergeDeepPropertiesKeys = ['headers', 'auth', 'proxy'];
var defaultToConfig2Keys = [
'baseURL', 'url', 'transformRequest', 'transformResponse', 'paramsSerializer',
'timeout', 'withCredentials', 'adapter', 'responseType', 'xsrfCookieName',
'xsrfHeaderName', 'onUploadProgress', 'onDownloadProgress',
'maxContentLength', 'validateStatus', 'maxRedirects', 'httpAgent',
'httpsAgent', 'cancelToken', 'socketPath'
];
utils.forEach(valueFromConfig2Keys, function valueFromConfig2(prop) {
if (typeof config2[prop] !== 'undefined') {
config[prop] = config2[prop];
}
});
utils.forEach(mergeDeepPropertiesKeys, function mergeDeepProperties(prop) {
if (utils.isObject(config2[prop])) {
config[prop] = utils.deepMerge(config1[prop], config2[prop]);
} else if (typeof config2[prop] !== 'undefined') {
config[prop] = config2[prop];
} else if (utils.isObject(config1[prop])) {
config[prop] = utils.deepMerge(config1[prop]);
} else if (typeof config1[prop] !== 'undefined') {
config[prop] = config1[prop];
}
});
utils.forEach(defaultToConfig2Keys, function defaultToConfig2(prop) {
if (typeof config2[prop] !== 'undefined') {
config[prop] = config2[prop];
} else if (typeof config1[prop] !== 'undefined') {
config[prop] = config1[prop];
}
});
var axiosKeys = valueFromConfig2Keys
.concat(mergeDeepPropertiesKeys)
.concat(defaultToConfig2Keys);
var otherKeys = Object
.keys(config2)
.filter(function filterAxiosKeys(key) {
return axiosKeys.indexOf(key) === -1;
});
utils.forEach(otherKeys, function otherKeysDefaultToConfig2(prop) {
if (typeof config2[prop] !== 'undefined') {
config[prop] = config2[prop];
} else if (typeof config1[prop] !== 'undefined') {
config[prop] = config1[prop];
}
});
return config;
};