AJAX
异步的JavaScript与XML技术( Asynchronous JavaScript and XML )
Ajax 核心使用 XMLHttpRequest
(XHR)对象,首先由微软引入的一个特性;Ajax 不需要任何浏览器插件,能在不更新整个页面的前提下维护数据(可以向服务器请求额外的数据无需重载页面),但需要用户允许JavaScript在浏览器上执行。
XHR 对象用法
1 | var xhr = new XMLRequestHttp() // 通过XMLHttpRequest 构造函数来创建 |
open 方法
1 | xhr.open(method, url, async, user, password); |
-method
:要发送的请求类型 GET、POST、PUT、DELETE
。(必选)url
:请求的URL (必选)
- axync :布尔值,是否异步发送请求,默认true(true 时,已完成事务的通知可供事件监听使用,如果
xhr.multipart
为true,则此参数必须为true;false 时,send()方法直到收到答复前不会返回) user
:用户名用于认证用途 默认 nullpassword
:用户名用于认证用途 默认 null
setRequestHeader()
如需设置 Accpet 头部信息,可通过setRequestHeader()
方法来设置
Accpet
头部信息:告知客户端可以处理的内容类型,用 MIME类型 表示;服务端使用 Content-Type 通知客户端它的选择
媒体类型( MIME类型 ) :一种标准,用来表示文档、文件或字 节流的性质和格式。 完整类型列表- Content-Type :实体头部用于指示资源的 MIME 类型,告诉客户端实际返回的内容类型;浏览器会在某些情况下进行MIME查找,并不一定遵循此标题的值; 为了防止这种行为,可以将标题 X-Content-Type-Options 设置为
nosniff
。1
xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded')
- Content-Type :实体头部用于指示资源的 MIME 类型,告诉客户端实际返回的内容类型;浏览器会在某些情况下进行MIME查找,并不一定遵循此标题的值; 为了防止这种行为,可以将标题 X-Content-Type-Options 设置为
send 方法
data:作为请求主体发送的数据,如果不需要通过请求主体发送数据,则必须传 null
调用 send()发送请求,在收到响应后,响应的数据会自动填充XHR对象的属性
1 | xhr.send(data); |
responseText :从服务端返回的文本
1 | xhr.onload = function () { |
responseXML
如果响应的 Content-Type
为text/html
或 application/xml
,将保存包含响应数据的 XML DOM
文档,对于其它类型的数据则为 null
, 也可通过overrideMimeType() 强制 XHR 对象解析为 XML1
2
3
4
5
6
7
8
9// overrideMimeType() 用来强制解析 response 为 XML
xhr.overrideMimeType('text/xml');
xhr.onload = function () {
if (xhr.readyState === xhr.DONE) {
if (xhr.status === 200) { 8 console.log(xhr.responseXML);
}
}
};
status
返回响应的HTTP状态码,请求完成前值为0,如果XHR 对象出错 值也是0, 200 表示请求成功,304表示请求的资源并没有修改,可直接使用浏览器种缓存的数据。 其它状态信息
statusText
返回响应的HTTP状态说明,status
值为 200 时 statusText
为 “OK”
readyState
返回一个当前XHR对象所处的活动状态
值 | 状态 | 描述 |
---|---|---|
0 | UNSENT | 代理被创建,但尚未调用 open() 方法。 |
1 | OPENED | open() 方法已经被调用。 |
2 | HEADERS_RECEIVED | send() 方法已经被调用,并且头部和状态已经可获得。 |
3 | LOADING | 下载中;响应体部分正在被接收 responseText 属性已经包含部分数据。 |
4 | DONE | 下载操作已完成。 |
onreadystatechange
当 readyState变化时会触发次事件函数,如果使用 abort() 取消请求则次事件函数不会被触发1
2
3
4
5xhr.onreadystatechange = function () {
if(xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
console.log(xhr.responseText)
}
}
参考资料
封装示例
1 | // 创建 构造函数 |
XMLHttpRequest Level 2
相比于 老版本的 XMLHttpRequest
新增以下内容:
可以设置 HTTP 请求超时时间
1 | var xhr = XMLHttpRequest(); |
可以通过 FormData
发送表单数据
1 | // 实例化 FormData |
可以上传文件
FormData
除了可以添加字符串数据,也可以添加blob、file
类型的数据,因此可以用于上传文件。在浏览器中,一般是通过文件上传输入框来获取 file 对象,比如:
1
<input type="file" name='uploadFile' id="upload-file" />
1
2
3
4
5
6
7
8document.getElementById('upload-file')
.addEventListener('change', function () {
var formData = new FormData();
// 获取数据
formData.append('uploadFile', this.files[0])
xhr.send(formData)
})
支持跨域请求
- 浏览器默认是不允许跨域请求的,有时候又是必要的,在以前通常使用
JSONP
来解决(IE10 以下不支持) - 为了标准化跨域请求, W3C提出 跨域资源共享(CORS)前端无须修改代码,只需 服务器返回
Access-Control-Allow-Origin
响应头,指定允许对应的域,如果是公共资源可指定“*” CORS
默认不发送cookie
如果需要发送,前端需要设置withCredentials
属性,同时服务器需要 返回Access-Control-Allow-Credentials: true
1
xhr.withCredentials = true;
检测XHR是否支持CORS最简单的方式,就是检查是否存在
withCredentials
,再检测XDomainRequest
对象是否存在,即可兼顾所有浏览器1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19let createCORSRequest = (method,url)=>{
let var xhr = mew XMLHttpRequest();
if ('withCredentials' in xhr){
xhr.open(method,url,true);
}else if(typeof XDomainRequest != 'undefined'){
xhr = new XDomainRequest();
xhr.open(method,url);
}else{
xhr = null
}
return xhr
}
let request = createCORSRequest('get','baidu.com')
if(request){
request.onload = function(){
// request.responseText
}
request.send()
}Preflighted Requests:
- 一个透明服务器验证机制,用于检查服务器是否支持CORS
这是一个 OPTIONS 请求,使用了三个请求头 - Access-Control-Request-Method:请求自身使用的方法
- Access-Control-Request-Headers:自定义头部信息,多个头部以逗号分隔
- Origin报头:和简单请求相同,将请求的域发送给服务端,服务端再Access-Control-Allow-Origin 响应头中返回同样的域即可解决跨域问题。
img src特性:
- 一个网页可以从任何网页中加载图像,不用担心跨域问题,通过onload 和 onerror 事件处理确定是否接收到响应
- 请求的数据通过查询字符串形式发送,响应可以是任意内容,通常是像素图或204响应。
- 只能发送 get 请求,无法访问服务器的响应文本
1
2
3
4
5let img = new Image();
img.onload = function (){
console.log('done')
}
img.src = 'http://www.baidu.com?test=test1'
请求数据
可以获取服务端二进制数据
- 使用
overrideMimeType
方法覆写服务器指定的MIME
类型,从而改变浏览器解析数据的方式1
2
3
4
5
6// 参数 MIME 类型
// 告诉浏览器,服务器响应的内容是用户自定义的字符集
xhr.overrideMimeType('text/plain; charset=x-user-defined');
// 浏览器就会将服务器返回的二进制数据当成文本处理,我们需要做进一步的转换才能拿到真实的数据
// 获取二进制数据的第 i 位的值
var byte = xhr.responseText.charCodeAt(i) & 0xff
- “& 0xff” 运算 参考 阮一峰的文章
- xhr.responseType 用于设置服务器返回的数据的类型,将
返回类型设置为 blob 或者 arraybuffer
,然后就可以从xhr.response
属性获取到对应类型的服务器返回数据。1
2
3
4
5xhr.responseType = 'arraybuffer'
xhr.onload = function () {
var arrayBuffer = xhr.response
// 接下来对 arrayBuffer 做进一步处理...
}
可以获取数据传输进度信息 参考资料
- 使用 onload 监听了一个数据传输完成的事件。
1
2
3
4
5
6
7// 上传进度监听
xhr.upload.addEventListener('progress', onProgressHandler, false);
// 传输成功完成
xhr.upload.addEventListener('load', onLoadHandler, false);
// 传输失败信息
xhr.upload.addEventListener('error', onErrorHandler, false);
兼容性
Axios
- 基于 Promise 的 Http 库
- 可以在客户端 和 nodeJs中使用
- 在客户端创基 XMLHttpRequests
- 在nodeJs 创建 HTTP 请求
- 支持Promise
- 可拦截转化请求和响应数据
- 取消请求
- 自动转化JSON数据
- 支持客户端 XSRF
兼容性
安装
1 | npm install axios |
methods
Get
1 | const axios = require('axios') |
同样的传参方法有 delete
post
1
2
3
4
5
6
7axios.post('url',{name:'Owen'})
.then(res =>{
console.log(res)
})
.catch(err =>{
console.log(err)
})同样的传参方法有 put patch
concurrent requests
1
axios.all([axios.get('url1'),axios.get('url2')])
API
axios(config)
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
axios({
method:'get', // default is get
url:'url', // request url
data:{ // 仅支持post,put和patch方法,数据作为请求主体发送 ( Only the post,put and patch methods are supported, and the data is sent as the request body )
/* 浏览器仅支持传递 FormData, File, Blob (The browser only supports passing FormData, File and Blob)
Node 仅支持传递 Stream, Buffer (The Node only supports passing Stream, Buffer)
*/
name:'owen'
},
baseURL:'base/url', // 除非url是绝对路径,否则将baseURL添加到url的前面 (Add baseURL to then front of the url unless the url is an absolute path)
transformRequest: [function (data, headers) {
// 可以修改发送的请求数据和请求头,只支持put,post和patch,回调函数必须返回Buffer,ArrayBuffer,FormData或Stream数据
// Can modify the sent request data and request header,only support put, post and patch.
// Callback must return Buffer, ArrayBuffer, FormData or Stream data
// Do whatever you want to transform the data
return data;
}],
transformResponse: [function (data) {
// 修改响应数据,再传递给 then或catch 方法 (Modify the response data and pass it to the then or catch method)
// Do whatever you want to transform the data
return data;
}],
headers: {'X-Requested-With': 'XMLHttpRequest'}, // 自定义请求头 (Custom request header)
params:{ // 添加到url尾部的参数,一般用于get 和 delete( Parameters addde to the end of the url,generally used for get and delete )
id:'xxx'
},
paramsSerializer: function (params) { //序列化 [params] (https://www.npmjs.com/package/qs)
return Qs.stringify(params, {arrayFormat: 'brackets'})
},
timeout:1000,// default is 0 , 设置请求超时时间,单位毫秒 ( Set request timeout in milliseconds )
withCredentials: true, // default is false, 跨域时是否携带cookie( Whether to carry cookies when crossing domains )
adapter: function (config) {
/*拦截响应数据*/
// At this point:
// - config has been merged with defaults
// - request transformers have already run
// - request interceptors have already run
// Make the request using config provided
// Upon response settle the Promise
return new Promise(function(resolve, reject) {
var response = {
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config: config,
request: request
};
settle(resolve, reject, response);
// From here:
// - response transformers will run
// - response interceptors will run
/**
* Resolve or reject a Promise based on response status.
*
* @param {Function} resolve A function that resolves the promise.
* @param {Function} reject A function that rejects the promise.
* @param {object} response The response.
*/
function settle(resolve, reject, response) {
var validateStatus = response.config.validateStatus;
if (!validateStatus || validateStatus(response.status)) {
resolve(response);
} else {
reject(createError(
'Request failed with status code ' + response.status,
response.config,
null,
response.request,
response
));
}
};
/**
* Create an Error with the specified message, config, error code, request and response.
*
* @param {string} message The error message.
* @param {Object} config The config.
* @param {string} [code] The error code (for example, 'ECONNABORTED').
* @param {Object} [request] The request.
* @param {Object} [response] The response.
* @returns {Error} The created error.
*/
function createError(message, config, code, request, response) {
var error = new Error(message);
return enhanceError(error, config, code, request, response);
}
/**
* Update an Error with the specified config, error code, and response.
*
* @param {Error} error The error to update.
* @param {Object} config The config.
* @param {string} [code] The error code (for example, 'ECONNABORTED').
* @param {Object} [request] The request.
* @param {Object} [response] The response.
* @returns {Error} The error.
*/
function enhanceError(error, config, code, request, response) {
error.config = config;
if (code) {
error.code = code;
}
error.request = request;
error.response = response;
error.isAxiosError = true;
error.toJSON = function() {
return {
// Standard
message: this.message,
name: this.name,
// Microsoft
description: this.description,
number: this.number,
// Mozilla
fileName: this.fileName,
lineNumber: this.lineNumber,
columnNumber: this.columnNumber,
stack: this.stack,
// Axios
config: this.config,
code: this.code
};
};
return error;
}
});
},
auth:{ // 表示应使用HTTP Basic身份验证,并提供凭据 ( indicates that HTTP Basic auth should be used, and supplies credentials. )
user:'xxx',
password:'***'
},
responseType: 'json',/* 服务器响应的数据类型( The server response data type )
支持 arraybuffer, blob, document, json, text, stream
*/
responseEncoding:'utf8', // 用于解码响应的编码 (Encoding for decoding the response )
xsrfCookieName: 'XSRF-TOKEN', // default is XSRF-TOKEN , csrf令牌Cookie 名称
xsrfHeaderName: 'X-XSRF-TOKEN', //default is X-XSRF-TOKEN, xsrf标记值的http标头的名称
onUploadProgress: function (progressEvent) { //上传进度事件 (handling of progress events for uploads )
console.log(progressEvent)
},
onDownloadProgress: function (progressEvent) { // 下载进度事件 ( handling of progress events for downloads)
console.log(progressEvent)
},
maxContentLength: 2000, // 允许响应内容的最大字节 (defines the max size of the http response content in bytes allowed)
validateStatus: function (status) { // 返回给定HTTP状态范围, 如果状态在给定范围内,响应数据传给`then` ,否则传给 `catch` ( Returns the given HTTP status range, if the status is within the give range, the respones data is passed to `then`, otherwise passed to `catch` )
return status >= 200 && status < 300; // default
},
maxRedirects: 5, // default is 5 // 定义Node 中最大重定向数 ( defines the maximunn number of redirects in Node )
socketPath: null, // default is null 定义要在node.js中使用的 UNIX socket
httpAgent: new http.Agent({ keepAlive: true }), // node 中 http 和 https 的代理
httpsAgent: new https.Agent({ keepAlive: true }),// http://nodejs.cn/api/http.html
proxy: { // 代理配置
host: '127.0.0.1',
port: 9000,
auth: {
username: 'mikeymike',
password: 'rapunz3l'
}
},
cancelToken: new CancelToken(function (cancel) { // 取消请求的 token
})
})
.then(res =>{
console.log(res)
})
.catch(err =>{
console.log(err)
})
全局配置
通过
axios.create
方法来替换全局配置1
2
3const instance = axios.create({
baseURL: 'base/url'
});通过
axios.defaults
对象替换全局默认配置1
2instance.defaults.headers.common['Authorization'] = AUTH_TOKEN;
instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
拦截器
拦截请求前的数据
1
2
3
4
5axios.interceptors.request.use(function (config) {
return config;
}, function (error) {
return Promise.reject(error);
});拦截响应数据
1
2
3
4
5
6
7axios.interceptors.response.use(function (response) {
// Do something with response data
return response;
}, function (error) {
// Do something with response error
return Promise.reject(error);
});删除拦截器
1
2const myInterceptor = axios.interceptors.request.use(function () {/*...*/});
axios.interceptors.request.eject(myInterceptor);
二次封装
1 | /** |