【axios三部曲】三、极简核心造轮子
axios执行流程
目录结构
如果要一个极简核心的话,我们只需要 request 请求函数,和一个 XMLHttpRequest 即可,拦截器,适配器 dispatchRquest 都可以不要
Axios构造函数
我们从Axios构造函数开始写
core/Axios.js
import utils from "../utils";
import mergeConfig from "./mergeConfig";
import xhr from "./xhr";
// 核心文件
function Axios(config) {
this.defaults = config;
}
Axios.prototype.request = function (config) {
// 处理axios(url[,config])
if (typeof config === "string") {
config = arguments[1] || {};
config.url = arguments[0];
} else {
config = config || {};
}
// 合并config
config = mergeConfig(this.defaults, config);
console.log(config);
// 设置默认method
if (config.method) {
config.method = config.method.toLowerCase();
} else if (this.defaults.method) {
config.method = this.defaults.method.toLowerCase();
} else {
config.method = "get";
}
let promise = Promise.resolve(config);
promise = promise.then(xhr);
return promise;
};
utils.forEach(["delete", "get", "head", "options"], function (method) {
Axios.prototype[method] = function (url, config) {
return this.request(
utils.merge(config || {}, {
method: method,
url: url,
})
);
};
});
utils.forEach(["post", "put", "patch"], function (method) {
Axios.prototype[method] = function (url, data, config) {
return this.request(
utils.merge(config || {}, {
method: method,
url: url,
data: data,
})
);
};
});
export default Axios;
只有一个最简单request方法
mergeConfig
core/mergeConfig.js
也没有做过多的判断,只是优先把config2的值拷贝到config上,其次再使用config1的值
import utils from "../utils";
// 合并两个配置对象,返回一个新的对象
function mergeConfig(config1, config2) {
let config = {};
let axiosKeys = Array.from(
new Set(Object.keys(config1).concat(Object.keys(config2)))
);
// 优先使用config2身上的
utils.forEach(axiosKeys, function (prop) {
if (utils.isObject(config2[prop])) {
config[prop] = utils.merge(config1[prop], config2[prop]);
} else if (typeof config2[prop] !== "undefined") {
config[prop] = config2[prop];
} else if (utils.isObject(config1[prop])) {
config[prop] = utils.merge(config1[prop]);
} else if (typeof config1[prop] !== "undefined") {
config[prop] = config1[prop];
}
});
return config;
}
export default mergeConfig;
xhr
core/xhr.js
一个简单的XMLHttpRequest
// XMLHttpRequest
// 返回一个promise
const xhr = function (config) {
return new Promise(function (resolve, reject) {
let xhr = new XMLHttpRequest();
xhr.open(config.method, config.url);
xhr.send(config.data);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
// 返回response数据,自定义格式
resolve({
config: config,
data: JSON.parse(xhr.responseText),
request: xhr,
headers: xhr.getAllResponseHeaders(),
status: xhr.status,
statusText: xhr.statusText,
});
} else {
reject("数据请求错误:" + xhr.statusText);
}
}
};
});
};
export default xhr;
utils
utils.js
一些工具函数
function forEach(obj, fn) {
if (obj === null || typeof obj === "undefined") {
return;
}
if (typeof obj !== "object") {
obj = [obj];
}
if (Array.isArray(obj)) {
for (let i = 0; i < obj.length; i++) {
fn.call(null, obj[i], i, obj);
}
} else {
for (let key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
fn.call(null, obj[key], key, obj);
}
}
}
}
function bind(fn, thisArg) {
// 这里用fn.apply来实现函数this绑定,注意需要返回的也是一个函数,所以这里包一层
return function wrap() {
// 把多个参数,转成数组的形式
let args = new Array(arguments.length);
for (let i = 0; i < args.length; i++) {
args[i] = arguments[i];
}
// args是一个数组,apply调用方式
return fn.apply(thisArg, args);
};
}
// 实现继承,b->a 如果是函数要绑定this指向
function extend(a, b, thisArg) {
forEach(b, function (val, key) {
if (thisArg && typeof val === "function") {
a[key] = bind(val, thisArg);
} else {
a[key] = val;
}
});
return a;
}
// 合并对象,返回一个新的对象
function merge() {
let result = {};
function assignValue(val, key) {
// result里的是一个对象,并且val也是一个对象,深拷贝
if (typeof result[key] === "object" && typeof val === "object") {
result[key] = merge(result[key], val);
} else if (typeof result[key] === "object") {
result[key] = merge({}, val);
} else {
result[key] = val;
}
}
// 有多个参数要合并,依次循环遍历,合并到result
for (let i = 0; i < arguments.length; i++) {
forEach(arguments[i], assignValue);
}
return result;
}
function isObject(val) {
return val !== null && typeof val === "object";
}
export default {
forEach,
bind,
extend,
merge,
isObject,
};
defaults
defaults.js
默认的头配置文件
let defaults = {
method: "get",
};
export default defaults;
axios入口文件
// 入口文件
import Axios from "./core/Axios";
import utils from "./utils";
import mergeConfig from "./core/mergeConfig";
import defaults from "./defaults";
function createInstance(defaultConfig) {
let context = new Axios(defaultConfig);
let instance = utils.bind(Axios.prototype.request, context);
// extend prototype
utils.extend(instance, Axios.prototype, context);
// extend Axios属性
utils.extend(instance, context);
instance.create = function (newConfig) {
return createInstance(mergeConfig(defaultConfig, newConfig));
};
return instance;
}
let axios = createInstance(defaults);
console.dir(axios);
// console.log(axios);
// axios({
// url: "http://localhost:3000/posts",
// method: "get",
// }).then((response) => {
// console.log(response);
// });
axios
.get("http://localhost:3000/posts", {
headers: {
common: 111,
},
})
.then((response) => {
console.log(response);
});
总结
- promise的使用
- utils里面的工具函数比较实用,要注意的是forEach,bind,extend,merge这几个函数
- 构造函数 + prototype的方式来组织代码,现在已经有更好的选择 class
- axios库的核心就是axios是一个函数(request),它身上也挂有方法(get,post),我们可以学习这种编程方法
- axios的config参数合并也是重点,有优先级,默认的最低,最后方法传入的最高