Wang Anyu's blog


  • 首页

  • 标签

  • 归档

拦截器 /lib/core/interceptorManager.js

发表于 2019-10-24

/lib/core/interceptorManager.js

interceptorManager.js定义了axios的拦截器,包含了拦截器的增加、删除、循环拦截器。响应拦截器和请求拦截器都是用数组存储的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
var utils = require('./../utils');

// 拦截器对象
function InterceptorManager() {
this.handlers = [];
}

// 添加一个新的拦截器到拦截器的数组中,两个参数分别为触发promise的then和reject的回调
// 返回当前拦截器在数组的下标作为id,id在移除拦截器时用到
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected
});
return this.handlers.length - 1;
};

// 通过传入id,移除拦截器数组中的对应拦截器
InterceptorManager.prototype.eject = function eject(id) {
if (this.handlers[id]) {
this.handlers[id] = null;
}
};

// 遍历所有注册的拦截器
InterceptorManager.prototype.forEach = function forEach(fn) {
utils.forEach(this.handlers, function forEachHandler(h) {
if (h !== null) {
fn(h);
}
});
};

module.exports = InterceptorManager;

axios源码中实际调用请求的地方 /lib/core/dispatchRequest.js

发表于 2019-10-23

/lib/core/dispatchRequest.js

dispatchRequest.js 文件是axios源码中实际调用请求的地方

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

var utils = require('./../utils');
var transformData = require('./transformData');
var isCancel = require('../cancel/isCancel');
var defaults = require('../defaults');

// 如果请求被取消,则抛出已取消
function throwIfCancellationRequested(config) {
if (config.cancelToken) {
config.cancelToken.throwIfRequested();
}
}

// 使用可配置的转换器想服务器发起请求
module.exports = function dispatchRequest(config) {
throwIfCancellationRequested(config);

// Ensure headers exist
config.headers = config.headers || {};

// Transform request data
// 使用config.transformRequest对data和headers进行格式化,具体见/lib/default.js
config.data = transformData(
config.data,
config.headers,
config.transformRequest
);

// 对不同配置的headers进行合并,有前到后优先级增高
config.headers = utils.merge(
config.headers.common || {},
config.headers[config.method] || {},
config.headers || {}
);

// 删除headers中的method属性
utils.forEach(
['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
function cleanHeaderConfig(method) {
delete config.headers[method];
}
);

// 如果配置了adapter,则用配置的adapter替换默认方法
var adapter = config.adapter || defaults.adapter;

return adapter(config).then(function onAdapterResolution(response) {
throwIfCancellationRequested(config);

// 格式化返回值的data呵headers
response.data = transformData(
response.data,
response.headers,
config.transformResponse
);

return response;
}, function onAdapterRejection(reason) {
// 请求失败的回调处理
if (!isCancel(reason)) {
throwIfCancellationRequested(config);

// Transform response data
if (reason && reason.response) {
reason.response.data = transformData(
reason.response.data,
reason.response.headers,
config.transformResponse
);
}
}

return Promise.reject(reason);
});
};

axios的默认请求头 /lib/defaults.js

发表于 2019-10-22

/lib/defaults.js

defaults.js文件中配置了axios的默认请求头,不同环境下axios的默认请求方法,格式化请求正文的方法以及响应征文的方法等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
var utils = require('./utils');
var normalizeHeaderName = require('./helpers/normalizeHeaderName');

var PROTECTION_PREFIX = /^\)\]\}',?\n/;

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

// 在没有设置content-type的情况下,设置content-type
function setContentTypeIfUnset(headers, value) {
if (!utils.isUndefined(headers) && utils.isUndefined(headers['Content-Type'])) {
headers['Content-Type'] = value;
}
}

// 根据当前环境,获取默认的请求方法
function getDefaultAdapter() {
var adapter;
if (typeof XMLHttpRequest !== 'undefined') {
// For browsers use XHR adapter
adapter = require('./adapters/xhr');
} else if (typeof process !== 'undefined') {
// For node use HTTP adapter
adapter = require('./adapters/http');
}
return adapter;
}

var defaults = {
adapter: getDefaultAdapter(),

// 格式化请求,在请求发送前使用
transformRequest: [function transformRequest(data, headers) {
normalizeHeaderName(headers, 'Content-Type');
// 属性名为不规则大小的content-type格式化为正确的Content-Type
// module.exports = function normalizeHeaderName(headers, normalizedName) {
// utils.forEach(headers, function processHeader(value, name) {
// if (name !== normalizedName && name.toUpperCase() === normalizedName.toUpperCase()) {
// headers[normalizedName] = value;
// delete headers[name];
// }
// });
// };

// formdata,二进制数组,流媒体,文件,二进制大对象
if (utils.isFormData(data) ||
utils.isArrayBuffer(data) ||
utils.isStream(data) ||
utils.isFile(data) ||
utils.isBlob(data)
) {
return data;
}
if (utils.isArrayBufferView(data)) {
return data.buffer;
}

// 如果是一个urlSearchParams对象,例如 new URLSearchParams("key1=value1&key2=value2")
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') {
data = data.replace(PROTECTION_PREFIX, '');
try {
data = JSON.parse(data);
} catch (e) { /* Ignore */ }
}
return data;
}],

timeout: 0,

// scrf设置的cookie的key和header的key
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',

maxContentLength: -1,

// 验证请求状态,在处理请求的时会调用,之前文章也有提到
validateStatus: function validateStatus(status) {
return status >= 200 && status < 300;
}
};

defaults.headers = {
common: {
'Accept': 'application/json, text/plain, */*'
}
};

utils.forEach(['delete', 'get', 'head'], function forEachMehtodNoData(method) {
defaults.headers[method] = {};
});

// 为post,put,patch请求设置默认的Content-Type
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
defaults.headers[method] = utils.merge(DEFAULT_CONTENT_TYPE);
});

module.exports = defaults;

axios的入口方法,实例化返回的axios

发表于 2019-10-21

实例化返回的axios

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
var utils = require('./utils');
var bind = require('./helpers/bind');
var Axios = require('./core/Axios');
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
// bind为自定义的方法,返回一个函数 () => Axios.prototype.request.apply(context.args)
// 实际上将Axios.prototype.request的执行上下文绑定到context上
var instance = bind(Axios.prototype.request, context);

// Copy axios.prototype to instance, axios自带的工具类
// 将Axios.prototype上的所有方法的执行上下文绑定到context,并且继承给instance
utils.extend(instance, Axios.prototype, context);
// Copy context to instance
// context继承给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(utils.merge(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;

module.exports.default = axios;

util.js 中被用到的工具类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* Extends object a by mutably adding to it the properties of object b.
*
* @param {Object} a The object to be extended
* @param {Object} b The object to copy properties from
* @param {Object} thisArg The object to bind function to
* @return {Object} The resulting value of object a
*/
function extend(a, b, thisArg) { // 将b的属性添加到a上面,thisArg转移属性为函数时绑定的执行上下文
forEach(b, function assignValue(val, key) {
if (thisArg && typeof val === 'function') {
a[key] = bind(val, thisArg); // 替换执行上下文
} else {
a[key] = val;
}
});
return a;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* Example:
* var result = merge({foo: 123}, {foo: 456});
* console.log(result.foo); // outputs 456
*
* @param {Object} obj1 Object to merge
* @returns {Object} Result of all merge properties
* 合并多个对象为一个对象,同名属性取最后的值
*/
function merge(/* obj1, obj2, obj3, ... */) {
var result = {};
function assignValue(val, key) {
if (typeof result[key] === 'object' && typeof val === 'object') {
result[key] = merge(result[key], val);
} else {
result[key] = val;
}
}

for (var i = 0, l = arguments.length; i < l; i++) {
forEach(arguments[i], assignValue);
}
return result;
}

bind 方法:

1
2
3
4
5
6
7
8
9
module.exports = function bind(fn, thisArg) {
return function wrap() {
var args = new Array(arguments.length);
for (var i = 0; i < args.length; i++) {
args[i] = arguments[i];
}
return fn.apply(thisArg, args);
};
};

所以createInstance 干了什么

instance有Axios.prototype.request和Axios.prototype的方法,并且这些方法的执行上下文都绑定到context中

instance 里面还有 context 上的方法。

/lib/core/axios.js axios实例上的各个方法

发表于 2019-10-20

/lib/core/Axios.js

Axios.js中定义了Axios实例上的request,get,post,delete方法。get,post,delete等方法均基于Axios.prototype.request的封装。在Axios.prototype.request会依次执行请求拦截器,dispatchRequest,响应拦截器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
var defaults = require('./../defaults');
var utils = require('./../utils');
var InterceptorManager = require('./InterceptorManager');
var dispatchRequest = require('./dispatchRequest');
var isAbsoluteURL = require('./../helpers/isAbsoluteURL');
var combineURLs = require('./../helpers/combineURLs');

// 创建一个新的Axios实例,参数为默认的实例配置
function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(), // 请求拦截器
response: new InterceptorManager() // 响应拦截器
};
}

// 对于请求的特殊配置,和默认配置进行合并
Axios.prototype.request = function request(config) {
// 如果config是一个字符串,则把config处理为请求的url
if (typeof config === 'string') {
config = utils.merge({
url: arguments[0]
}, arguments[1]);
}

// 默认指定get方法
config = utils.merge(defaults, this.defaults, { method: 'get' }, config);

// 有baseUrl并且配置的url不是绝对路径时,将url转为baseUrl开头的路径
if (config.baseURL && !isAbsoluteURL(config.url)) {
config.url = combineURLs(config.baseURL, config.url);
}

// Hook up interceptors middleware
var chain = [dispatchRequest, undefined];
var promise = Promise.resolve(config);
// 将请求拦截器,响应拦截器,以及实际请求dispatchRequest方法组合成数组,如下
// [请求拦截器1success,请求拦截器1error,请求拦截器2success,请求拦截器2error,dispatchRequest,undefined,
// 响应拦截器1success,响应拦截器1error,响应拦截器2success,响应拦截器2error]
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});

this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
chain.push(interceptor.fulfilled, interceptor.rejected);
});

// 开始执行整个请求流程 请求拦截器 => dispatchRequest => 响应拦截器
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}

return promise;
};

// 提供其他的请求方法
utils.forEach(['delete', 'get', 'head'], function forEachMethodNoData(method) {
/*eslint func-names:0*/
Axios.prototype[method] = function(url, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url
}));
};
});

utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
/*eslint func-names:0*/
Axios.prototype[method] = function(url, data, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url,
data: data
}));
};
});

module.exports = Axios;

adapter_xhr.js 默认的请求方法

发表于 2019-10-19

/lib/adapter/xhr.js

xhr.js 暴露的xhrAdapter方法,是axios在浏览器环境下的默认请求方法,可以在配置中使用adapter配置项对默认的方法进行替换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
module.exports = function xhrAdapter(config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
var requestData = config.data;
var requestHeaders = config.headers;

// 判断是不是formData对象,如果是,删除header的Content-Type值,由浏览器自己决定
if (utils.isFormData(requestData)) {
delete requestHeaders['Content-Type']; // Let the browser set it
}

// 创建xml对象以及监听事件的名
var request = new XMLHttpRequest();
var loadEvent = 'onreadystatechange';
var xDomain = false;

// For IE 8/9 CORS support 针对ie8/9的cors支持
// 只支持post和get的调用,并且不会返回相应的头
// 对于test环境,或者没有XDomainRequest
// 对以上情况进行特殊处理
// Only supports POST and GET calls and doesn't returns the response headers.
// DON'T do this for testing b/c XMLHttpRequest is mocked, not XDomainRequest.
if (process.env.NODE_ENV !== 'test' &&
typeof window !== 'undefined' &&
window.XDomainRequest && !('withCredentials' in request) &&
!isURLSameOrigin(config.url)) {
request = new window.XDomainRequest();
loadEvent = 'onload';
xDomain = true;
request.onprogress = function handleProgress() {};
request.ontimeout = function handleTimeout() {};
}

// 设置http请求头中的Authorization字段
// HTTP basic authentication
if (config.auth) {
var username = config.auth.username || '';
var password = config.auth.password || '';
requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password);
}

// 初始化请求方法
// open(method, url, 是否支持异步)
request.open(config.method.toUpperCase(),
buildURL(config.url, config.params, config.paramsSerializer), true);

// 设置超时时间
request.timeout = config.timeout;

// Listen for ready state
// 监听3readyState状态的变化,状态为4时才继续下一步
request[loadEvent] = function handleLoad() {
if (!request || (request.readyState !== 4 && !xDomain)) {
return;
}

// 当这个请求出错,并且没有返回值时(request.status为0),会触发onerror事件
// 有一个情况除外,当请求使用了文件的协议时,即使发送成功,这个status也会为0
if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) {
return;
}

// 转化响应头部
var responseHeaders = 'getAllResponseHeaders' in request ?
parseHeaders(request.getAllResponseHeaders()) : null;

// 如果没有配置响应类型,或者响应类型为text,那么返回request.responseText, 否则返回request.response
// responseType是一个枚举类型,手动设置返回数据的类型
// responseText是全部后端的返回数据为纯文本的值
// response为正文,response的类型取决于responseType
var responseData = !config.responseType || config.responseType === 'text' ?
request.responseText : request.response;
var response = {
data: responseData,
// IE sends 1223 instead of 204 (https://github.com/mzabriskie/axios/issues/201)
status: request.status === 1223 ? 204 : request.status,
statusText: request.status === 1223 ? 'No Content' : request.statusText,
headers: responseHeaders,
config: config,
request: request
};

settle(resolve, reject, response);
// 校验请求结果
// if (!response.status || !validateStatus || validateStatus(response.status)) {
// resolve(response);
// } else {
// reject(createError(
// 'Request failed with status code ' + response.status,
// response.config,
// null,
// response
// ));
// }

// Clean up request
request = null;
};

// ajax失败时触发
request.onerror = function handleError() {
reject(createError('Network Error', config));
// 抛出network error的错误

// Clean up request
request = null;
};

// 请求超时的时候触发
request.ontimeout = function handleTimeout() {
reject(createError('timeout of ' + config.timeout + 'ms exceeded', config, 'ECONNABORTED'));

// Clean up request
request = null;
};

// 添加xsrf头部,只有在标准浏览器环境下才会执行
// 尤其是web worker或者RN的环境下不会执行
// xsrf header用来防御xsrf攻击,原理是服务端生成一个xsrf-token,浏览器在每次访问的时候都带上这个属性作为header,
// 服务器会比较cookie中的这个值和header的这个值是否一致
// 根据通源策略,非本源的网站无法读取修改本源的网站cookie,避免cookie被伪造
if (utils.isStandardBrowserEnv()) {
var cookies = require('./../helpers/cookies');

// Add xsrf header
var xsrfValue = (config.withCredentials || isURLSameOrigin(config.url)) && config.xsrfCookieName ?
cookies.read(config.xsrfCookieName) :
undefined;

if (xsrfValue) {
requestHeaders[config.xsrfHeaderName] = xsrfValue;
}
}

// Add headers to the request
if ('setRequestHeader' in request) {
utils.forEach(requestHeaders, function setRequestHeader(val, key) {
if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') {
// Remove Content-Type if data is undefined
delete requestHeaders[key];
} else {
// Otherwise add header to the request
request.setRequestHeader(key, val);
}
});
}

// 不同域下的XmlHttpRequest请求,无论Access-Cntrol-header设置成什么值,都无法改变自身站点下的cookie,
// 除非将withCreden设置为true
if (config.withCredentials) {
request.withCredentials = true;
}

// Add responseType to request if needed
if (config.responseType) {
try {
request.responseType = config.responseType;
} catch (e) {
if (request.responseType !== 'json') {
throw e;
}
}
}

// 下载进度
if (typeof config.onDownloadProgress === 'function') {
request.addEventListener('progress', config.onDownloadProgress);
}

// 上传进度 request.upload返回一个XMLHttpRequestUpload对象,用来表示上传进度
if (typeof config.onUploadProgress === 'function' && request.upload) {
request.upload.addEventListener('progress', config.onUploadProgress);
}

if (config.cancelToken) {
// 取消请求
config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) {
return;
}

request.abort();
reject(cancel);
// Clean up request
request = null;
});
}

if (requestData === undefined) {
requestData = null;
}

// Send the request
request.send(requestData);
});
};

axios的请求取消操作 /lib/cancel/cancelToken.js

发表于 2019-10-18

具体使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// axios用于取消请求的类
const CancelToken = axios.CancelToken
// source方法会返回一个对象,对象包含
// {
// token, 添加到请求的config,用于标识请求
// cancel, 调用cancel方法取消请求
// }
const source = CancelToken.source()

axios.get('/info', {
cancelToken: source.token
}).catch(function(error) {
if (axios.isCancel(error)) {
console.log('取消请求的错误')
} else {
// 其他错误
}
})

// 调用source.cancel可以取消axios.get('/info')的请求
source.cancel('取消请求')

查看源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
function CancelToken(executor) {  // @param {Function} executor The executor function.
if (typeof executor !== 'function') {
throw new TypeError('executor must be a function.');
}

var resolvePromise;
// 在调用cancel方法之前一直处于pending状态
this.promise = new Promise(function promiseExecutor(resolve) {
resolvePromise = resolve;
});

var token = this;
executor(function cancel(message) {
if (token.reason) {
// Cancellation has already been requested
return;
}
// 添加取消的理由
token.reason = new Cancel(message);
// 结束pending状态,设置为resolve
resolvePromise(token.reason);
});
}

// 判断该请求是否已经被取消的方法
CancelToken.prototype.throwIfRequested = function throwIfRequested() {
if (this.reason) {
throw this.reason;
}
}

/**
* Returns an object that contains a new `CancelToken` and a function that, when called,
* cancels the `CancelToken`.
* 返回一个对象,包含一个CancelToken对象,和让他请求取消的方法cancel
*/
CancelToken.source = function source() {
var cancel;
var token = new CancelToken(function executor(c) {
cancel = c;
});
return {
token: token,
cancel: cancel
};
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// xhr.js 相关代码

if (config.cancelToken) {
// Handle cancellation
// 当调用了cancelToken.source返回对象中的cancel时,触发回调
config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) {
return;
}

request.abort(); // 取消请求
reject(cancel);
// Clean up request
request = null;
});
}

总结

通过cancelToken,创建了一个额外的promiseA,这个promiseA对外暴露了它的resolve方法,这个promise被挂载在config下。
在调用send方法前,添加对promiseA的监听,当promiseA的状态发生变化,就会在promiseA的callback取消请求,并且将axios返回的的promiseB的状态设置为reject,从而取消请求。

Hello World

发表于 2019-10-15

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

mergeOptions合并策略

发表于 2018-12-06

mergeOptions方法阅读

mergeOptions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// 合并构造函数原本的options和构造时传入的options
export function mergeOptions (
parent: Object, // 构造函数上的options
child: Object, // 构造时传入的options
vm?: Component // 实例化本身
): Object {
if (process.env.NODE_ENV !== 'production') {
checkComponents(child) // 方法内容见下文
// 检查组件名是否合法
}

if (typeof child === 'function') {
child = child.options
}

normalizeProps(child, vm)
normalizeInject(child, vm)
normalizeDirectives(child)
// 这三个方法的作用类似,都是讲props,inject,directives属性转为对象的形式,例如有时候会传入数组的形似:
// Vue.component('blog-post', {
// props: ['postTitle'],
// template: '<h3>{{ postTitle }}</h3>'
// })

const extendsFrom = child.extends
if (extendsFrom) {
parent = mergeOptions(parent, extendsFrom, vm)
}
// 上下部分代码:子options如果有mixins或者extends,则需要重新调用mergeOptions来合并这些属性
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm)
}
}
const options = {}
let key
for (key in parent) {
mergeField(key)
}
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key)
}
}
function mergeField (key) {
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)
}
// defaultStrat 默认合并策略, 子没有就取父的,子有就取子的
/*const defaultStrat = function (parentVal: any, childVal: any): any {
return childVal === undefined
? parentVal
: childVal
}*/

checkComponents方法

检查构造时传入的options中,components中引入的组件名是否合法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function checkComponents (options: Object) {
for (const key in options.components) {
// 遍历传入options中的components,并验证引入的组件的合法性
validateComponentName(key)
}
}

export function validateComponentName (name: string) {
if (!/^[a-zA-Z][\w-]*$/.test(name)) { // 命名是否符合正则中的规则
warn(
'Invalid component name: "' + name + '". Component names ' +
'can only contain alphanumeric characters and the hyphen, ' +
'and must start with a letter.'
)
}
if (isBuiltInTag(name) || config.isReservedTag(name)) {
// isBuiltInTag方法是检验该名是否与svg标签或者html标签相同
// isReservedTag方法,检验是否和保留字相同
warn(
'Do not use built-in or reserved HTML elements as component ' +
'id: ' + name
)
}
}

将props统一处理为对象形式的方法,inject和directive具体思路类似,区别在于各自特有的小部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
function normalizeProps (options: Object, vm: ?Component) {
const props = options.props
if (!props) return
const res = {}
let i, val, name
if (Array.isArray(props)) {
i = props.length
while (i--) {
val = props[i]
if (typeof val === 'string') {
name = camelize(val) // 短横线转驼峰
res[name] = { type: null }
} else if (process.env.NODE_ENV !== 'production') {
warn('props must be strings when using array syntax.')
}
}
} else if (isPlainObject(props)) {
// 验证是否是对象,isPlainObject内部调用的是Object.prototype.tostring.call()和[object Object]字符串进行对比
for (const key in props) {
val = props[key]
name = camelize(key)
if (process.env.NODE_ENV !== 'production' && isPlainObject(val)) {
validatePropObject(name, val, vm)

/* const propOptionsRE = /^(type|default|required|validator)$/
校验prop对象中,类似type或者required等是否正确合法
function validatePropObject (
propName: string,
prop: Object,
vm: ?Component
) {
for (const key in prop) {
if (!propOptionsRE.test(key)) {
warn(
`Invalid key "${key}" in validation rules object for prop "${propName}".`,
vm
)
}
}
}*/

}
res[name] = isPlainObject(val)
? val
: { type: val }
}
} else if (process.env.NODE_ENV !== 'production') {
warn(
`Invalid value for option "props": expected an Array or an Object, ` +
`but got ${toRawType(props)}.`,
vm
)
}
options.props = res
}

几种合并策略

钩子函数的合并策略

1
2
3
4
5
6
7
8
9
10
11
12
function mergeHook (
parentVal: ?Array<Function>,
childVal: ?Function | ?Array<Function>
): ?Array<Function> {
return childVal
? parentVal
? parentVal.concat(childVal)
: Array.isArray(childVal)
? childVal
: [childVal]
: parentVal
}

流畅图如下

钩子函数合并策略mergehook

props,methods,inject,computed 的合并

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
strats.props =
strats.methods =
strats.inject =
strats.computed = function (
parentVal: ?Object,
childVal: ?Object,
vm?: Component,
key: string
): ?Object {
if (childVal && process.env.NODE_ENV !== 'production') {
assertObjectType(key, childVal, vm) // 校验是否是对象的方法
}
if (!parentVal) return childVal
const ret = Object.create(null)
extend(ret, parentVal)
if (childVal) extend(ret, childVal)
// 合并并生成新的对象
return ret
}

流程和钩子函数合并类似,只是处理父子合并的时候不同,此处是使用继承的方式,同名属性使用childVal中的值

components,directives,filters 的合并

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function mergeAssets (
parentVal: ?Object,
childVal: ?Object,
vm?: Component,
key: string
): Object {
const res = Object.create(parentVal || null)
if (childVal) {
process.env.NODE_ENV !== 'production' && assertObjectType(key, childVal, vm)
return extend(res, childVal)
} else {
return res
}
}

逻辑同上

data,computed的合并

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
export function mergeDataOrFn (
parentVal: any,
childVal: any,
vm?: Component
): ?Function {
if (!vm) { // 如果调用mergeOptions操作的不是vm实例,一般通过Vue.extend或者Vue.component调用
// in a Vue.extend merge, both should be functions
if (!childVal) {
return parentVal
}
if (!parentVal) {
return childVal
}
// when parentVal & childVal are both present,
// we need to return a function that returns the
// merged result of both functions... no need to
// check if parentVal is a function here because
// it has to be a function to pass previous merges.
// 下面的操作目的,都是想要封装返回为函数
return function mergedDataFn () {
return mergeData(
typeof childVal === 'function' ? childVal.call(this, this) : childVal,
typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal
)
}
} else { // 调用mergeOptions操作的是vm实例,一般通过new Vue来创建调用
return function mergedInstanceDataFn () {
// instance merge
const instanceData = typeof childVal === 'function'
? childVal.call(vm, vm)
: childVal
const defaultData = typeof parentVal === 'function'
? parentVal.call(vm, vm)
: parentVal
if (instanceData) {
return mergeData(instanceData, defaultData)
} else {
return defaultData
}
}
}
}

上面代码的最后,都会调用mergeData,下面是mergeData内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function mergeData (to: Object, from: ?Object): Object {
if (!from) return to
let key, toVal, fromVal
const keys = Object.keys(from)
for (let i = 0; i < keys.length; i++) {
key = keys[i]
toVal = to[key]
fromVal = from[key]
if (!hasOwn(to, key)) {
set(to, key, fromVal)
} else if (isPlainObject(toVal) && isPlainObject(fromVal)) {
mergeData(toVal, fromVal)
}
}
return to
}

to为childVal,from为parentVal
大体思路:

  • 没有parent,直接返回child
  • 有parent,遍历parent,如果child没有就直接set,
  • 如果有child,如果都是对象,递归调用mergeData,不是对象以child为准
Wang Anyu

Wang Anyu

9 日志
4 标签
© 2019 Wang Anyu
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.4