在学习ajax之前,我们需要了解一个对象。
XMLHttpRequest对象
XMLHttpRequest对象是浏览器请求的核心,该对象提供了对HTTP协议的完全访问,用于请求或发送数据,可以同步或异步的返回web服务器的响应,利用这一点,可以实现页面无刷新更新数据。
所有浏览器都支持,但是,在IE 5 和 IE 6 中,必须使用特定于 IE 的 ActiveXObject() 构造函数。
创建一个简单的XMLHttpRequest对象:
// 老版本的 Internet Explorer (IE5 和 IE6)使用 ActiveX 对象
let xhr = new XMLHttpRequest() || new ActiveXObject('Microsoft.XMLHTTP');
xhr.open('post',url,true)
xhr.send('hello')
xhr.onreadystatechange = function() {
if(xhr.readyState == 4 && xhr.status == 200){
console.log('hello')
}
}
属性
readyState:HTTP 请求的状态.当一个XMLHttpRequest初次创建时,这个属性的值从 0 开始,直到接收到完整的 HTTP 响应,这个值增加到 4。状态如下:- 0 :初始化状态。
XMLHttpRequest对象已创建或已被abort()方法重置。 - 1 :
open()方法已调用,但是send()方法未调用。请求还没有被发送。 - 2 :
Send()方法已调用,HTTP 请求已发送到 Web 服务器。未接收到响应。 - 3 :所有响应头部都已经接收到。响应体开始接收但未完成。
- 4 :HTTP 响应已经完全接收。
- 0 :初始化状态。
readyState的值不会递减,除非当一个请求在处理过程中的时候调用了abort()或open()方法。每次这个属性的值增加的时候,都会触发onreadystatechange事件句柄。
-
responseText: 目前为止为服务器接收到的响应体(不包括头部),字符串形式,或者如果还没有接收到数据的话,就是空字符串。 -
responseXML: XML形式的响应体。 -
status: 由服务器返回的 HTTP 状态代码,如 200 表示成功。
事件
onreadystatechange:接收服务器响应数据,每当readyState这个属性值发生变化的时候,就会调用事件。
方法
open(method,url,async,username,password)
初始化HTTP请求。
methods:请求方法,包括GET,POST,HEAD。url:请求主体,大多数浏览器实施了一个同源安全策略,并且要求这个 URL 与包含脚本的文本具有相同的主机名和端口。async:指示请求使用应该异步地执行。如果这个参数是false,请求是同步的,后续对send()的调用将阻塞,直到响应完全接收。如果这个参数是true或省略,请求是异步的,且通常需要一个onreadystatechange事件句柄。- username 和 password 参数是可选的,为 url 所需的授权提供认证资格。
send(body)
发送一个 HTTP 请求。该请求由以下几部分组成:
-
之前调用
open()时指定的 HTTP 方法、URL 以及认证资格(如果有的话)。 -
之前调用
setRequestHeader()时指定的请求头部(如果有的话)。 -
传递给这个方法的
body参数。
body参数指定了请求体,作为一个字符串或者 Document 对象。如果请求体不适必须的话,这个参数就为 null。
如果之前没有调用open(),或者更具体地说,如果 readyState 不是 1,send()抛出一个异常。
如果之前调用的 open() 参数 async 为 false,这个方法会阻塞并不会返回,直到 readyState 为 4 并且服务器的响应被完全接收。如果 async 参数为 true,send() 立即返回。
一旦请求发布了,send() 把 readyState 设置为 2,并触发 onreadystatechange 事件句柄。
setRequestHeader(name, value)
- name 参数是要设置的头部的名称。这个参数不应该包括空白、冒号或换行。
- value 参数是头部的值。这个参数不应该包括换行。
setRequestHeader() 方法指定了一个 HTTP 请求的头部,它应该包含在通过后续 send() 调用而发布的请求中。这个方法只有当 readyState 为 1 的时候才能调用
如果带有指定名称的头部已经被指定了,这个头部的新值就是:之前指定的值,加上逗号、空白以及这个调用指定的值。
get请求和post请求的区别
-
GET在浏览器back回退时不具影响,而POST会再次提交请求。 -
GET产生的URL地址可以被标记,而POST不可以。 -
GET请求会被浏览器主动缓存,而POST不会,除非手动设置。 -
GET请求只能进行url编码,而POST支持多种编码方式。 -
GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。 -
GET请求在URL中传送的参数是有长度限制的,而POST么有。 -
GET参数通过URL传递,POST放在Request body中。 -
对参数的数据类型,
GET只接受ASCII字符,而POST没有限制。 -
GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
AJAX
什么是ajax?
全称是Asynchronous[eɪ'sɪŋkrənəs] JavaScript and XML的缩写,它是指一种创建动态交互网页的开发技术。可以异步&请求发送数据,不需要重新刷新当前页面。
ajax的作用&目的?
作用是提高用户体验,减少网络数据传输。
ajax的技术体系组成(原理)
- 基于
web标准XHTML+CSS的表示 - 使用
DOM进行动态显示及交互 - 使用
XML和XSLT进行数据交换及相关操作 - 使用
XMLHttpRequest进行异步数据查询、检索 - 使用
JavaScript将所有东西绑定在一起
XML是什么?
全称:EXtensible Markup Language
HTML全称:
Hyper Text Markup Language> HTML,用来格式化并显示数据。
-
指可拓展的标记语言
-
用来传输和存储数据。
-
XML 仅仅是纯文本
-
XML 是不作为的。
-
XML是对HTML的补充
ajax封装
从以上原理,我们简单的来封装一个ajax:
// 格式化参数
function formateData(data){
let arr = []
for(let key in data){
arr.push(encodeURIComponent(key) + '=' + data[key])
}
reutrn arr.join('&')
}
function ajax(params){
params = params || {}
params.data = params.data || {}
params.type = { params.type || 'GET' }.toUpperCase()
params.data = formateData(params.data)
let xhr = new XMLHttpRequest() || new ActiveXObject('Microsoft.XMLHTTP');
if(params.type === 'GET'){
xhr.open(params.type, params.url+'?'+params.data, true)
xhr.send()
} else {
xhr.open(params.type, params.url, true)
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
xhr.send(params.data)
}
xhr.onreadystatechange = function() {
if(xhr.readyState == 4){
if(xhr.status == 200 || xhr.status == 304 || xhr.status == 206)
if (params.success && params.success instanceof Function) {
res = JSON.parse(xhr.responseText);
params.success.call(xhr, res);
}
} else {
if (params.error && params.error instanceof Function) {
res = xhr.responseText;
params.error.call(xhr, res);
}
}
}
}
ajax({
type:'POST',
url:'',
data:{
name:'yang'
},
success:function(res){},
error:function(err){},
})
ajax请求的步骤
从上面可以分析出ajax的几个步骤:
-
创建
XMLHttpRequest对象 -
注册回调函数
onreadystatechange -
使用
open()初始化请求信息,以及配置请求头 -
使用
send()发送请求信息 -
创建回调函数,用于接收服务器返回的数据,并响应页面。
ajax请求的callback
onSuccessonFailureonUninitializedonLoadingonloadedonInteractiveonCompleteonException
ajax与传统web应用的区别
传统的web应用:传统的web应用使用表单获取或发送数据,当点击提交的时候,发送数据,然后等待服务器响应请求,服务器会返回一个新的页面,页面重新加载,这对用户交互不太友好。
ajax:通过XMLHttpRequest对象与服务器进行交互。XMLHttpRequest对象可以接受服务器返回的信息,无需重新加载页面,就可以修改页面数据。如果是异步请求,也不会不阻塞加载。
ajax优点与缺点
优点:
- 无刷新更新数据,异步模式,提升了用户体验。
- 优化了浏览器和服务器之间的传输,更加具有迅速响应的能力。
- ajax运行在客户端,承担了一部分服务器的工作,减轻了服务器的负担。
缺点:
- ajax 不支持浏览器back按钮
- ajax暴露了与服务器交互的细节
- 对搜索引擎的支持比较弱
- 不容易调试
Axios
什么是axios?
axios是一个基于promise的HTTP库,可以在浏览器或者node环境中执行使用。
axios特点(优点)有哪些?
- 基于promise异步请求
- 浏览器端/node端都可以使用,浏览器创建XMLHttpRequest,node创建http请求。
- 支持请求/响应拦截
- 支持请求取消
- 可以转换请求数据和响应数据
- 可以批量发送多个请求
- 安全性高,支持防御XSRF。
XSRF:跨域请求伪造。axios中,会让每个请求都带一个从cookie中拿到的key,根据浏览器同源策源,假冒网站是拿不到cookie中的key的,这样,后台就可以轻松的辨别出这个请求是否是用户在假冒网站上的误导输入,从而采取正确的策源。
相较于axios,ajax的缺点?
- 本身是针对MVC模式的编程,不符合现在的MVVM模式。
- 基于原生的XHR开发,XHR本身的架构不清晰。
- 不符合关注分离的原则。
- 配置和调用混乱,而且基于事件的异步模型不友好。
axios语法
axios({
method: 'post',
url: '/user/12345',
data: {
firstName: 'Fred',
lastName: 'Flintstone'
}
}).then(()=>{})
axios.get('http://127.07.01',config).then(()=>{})
// 需要注意的是,如果get请求要传参,需要写在config里
axios.get('http://127.07.01',{
params:data
}).then(()=>{})
axios.post('http://127.07.01',params,config).then(()=>{})
常用的语法
- axios(config):通用
- axios(url,[config]):可以指定URL发送get请求
- axios.request(config):等于axios(config)
- axios.get(url,[config]):get请求
- axios.post(url,[data,config]):post请求
- axios.put(url,[data,config]):put请求
- axios.default.xxx:默认的全局配置
- axios.interceptors.request.use():请求拦截器
- axiox.interceptors.responese.use():响应拦截器
- axios.create([config]):创建一个新的axios
- axios.Cancel():
- axios.all(promise):用于批量执行多个异步请求
- axios.spread():指定接收所有成功数据的回调函数的方法。
axios的一些功能
入口
- 创建一个instance实例,绑定Axios中的原型方法,并将this指向Axios。
var defaults = require('./defaults');
function createInstance(defaultConfig) {
//这里创建了axios的实例
var context = new Axios(defaultConfig);
var instance = bind(Axios.prototype.request, context);
//这里用了bind方法改变了Axios.prototype.request,this的指向,返回了新的函数;
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);
};
};
var axios = createInstance(defaults);
默认配置
- 判断当前环境
- 对请求头的一些配置
- url:请求的服务器URL
- headers:请求头
- transformRequest:允许在向服务器发送前,修改请求数据,只能用在'PUT','POST'和'PATCH'这几个请求方法
- method:请求方法
- params:请求参数
- auth:身份验证
- 当有自定义配置,使用merge方法把默认配置替换成自定义
//这就是axios是如何支持浏览器和node端的原因
function getDefaultAdapter() {
var adapter;
if (typeof XMLHttpRequest !== 'undefined') {
// 对弈浏览器使用XMLHttpRequest
adapter = require('./adapters/xhr');
} else if (typeof process !== 'undefined') {
// node 使用 http
adapter = require('./adapters/http');
}
return adapter;
}
var defaults = {
adapter:getDefaultAdapter(),
}
defaults.headers = {
common: {
'Accept': 'application/json, text/plain, */*'
}
};
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
defaults.headers[method] = utils.merge(DEFAULT_CONTENT_TYPE);
});
浏览器的实现
- 当前环境是浏览器时,运行xhr.js模块
- xhr.js返回了一个promise对象,里面创建了
XMLHttpRequest对象,请求成功的时候调用promise的resolve,失败调用reject。 - 实现axios取消请求
function xhrAdapter(config) {
return new Promise(function dispatchXhrRequest(resolve,reject)=>{
var request = new XMLHttpRequest();
request.onreadystatechange = function handleLoad() {
if (!request || request.readyState !== 4) {
return;
}
if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) {
return;
}
// ...
settle(resolve, reject, response);
request = null;
}
//取消请求的地方
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;
}
//发送请求
request.send(data);
})
}
核心方法request()
- 将get,post等方法挂载到原型上。
- 不论调用get,put,post,最后调用的都是request()方法
- 请求发送前有一个数组chain,请求拦截的函数加到数组前面,请求响应后的函数加载到数组后面,然后按顺序调用数组。
Axios.prototype.request = function request(config) {
var chain = [dispatchRequest, undefined];
// 请求拦截
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);
});
// 循环执行所有方法
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
}
拦截器
定了handles数组收集了所有的拦截方法
作用?
- 请求拦截器时统一打开loading,响应拦截器关闭。
- 对请求时错误统一弹出toast
- 响应拦截器统一或单独处理响应数据格式
axios的取消功能
对于取消请求在创建axios的时候传入了取消的对象cancelToken,这个对象创建了一个promise并向外暴露了一个取消方法,当调用这个取消方法的时候,会把这个promise状态设置为成功,当成功的时候,会在xhr文件里调用 取消请求的 request.abort()方法。
vue中使用axios
在main.js中使用
import axios from 'axios'
/* 请求拦截器 */
axios.interceptors.request.use(
config => {
let token = localStorage.getItem('userToken')
if (token) {
config.headers.token = token
}
// config.url = process.env.NODE_ENV === 'production' ? apiMode.SERVICE_CONTEXT_PATH + config.url : 'apis' + config.url
// config.url = apiMode.SERVICE_CONTEXT_PATH + config.url
return config
},
error => {
return Promise.reject(error)
}
)
/**
* axios拦截器,后台响应是否需要重新登录
*/
axios.interceptors.response.use(
response => {
if (response) {
switch (response.data.code) {
case 11001:
login()
break
case 10004:
login()
break
}
}
return response
},
error => {
return Promise.reject(error.response.data) // 返回接口返回的错误信息
}
)
axios.defaults.withCredentials = true
axios.defaults.crossDomain = true
引申提问
为什么 axios 既可以当函数调用,也可以当对象使用,比如axios({})、axios.get?
axios本质上是函数,不过它有一些别名方法,函数和别名方法其实都是指向同一个方法 axios.request()
utils.forEach(
["delete", "get", "head", "options"],
function forEachMethodNoData(method) {
Axios.prototype[method] = function(url, config) {
return this.request(
utils.merge(config || {}, {
method: method,
url: url
})
);
};
}
);
utils.forEach(["post", "put", "patch"], function forEachMethodWithData(method) {
Axios.prototype[method] = function(url, data, config) {
return this.request(
utils.merge(config || {}, {
method: method,
url: url,
data: data
})
);
};
});
为什么支持浏览器中发送请求也支持node发送请求?
axios在浏览器端使用XMLHttpRequest对象发送请求;在node环境使用http对象发送请求。
var defaults.adapter = getDefaultAdapter();
function getDefaultAdapter () {
var adapter;
if (typeof XMLHttpRequest !== 'undefined') {
// 浏览器环境
adapter = require('./adapter/xhr');
} else if (typeof process !== 'undefined') {
// node环境
adapter = require('./adapter/http');
}
return adapter;
}
fetch
fetch(url).then(function(response) {
return response.json();
}).then(function(data) {
console.log(data);
}).catch(function(e) {
console.log("Oops, error");
});
fetch特点?
-
语法简洁,更加语义化
-
基于标准 Promise 实现,支持 async/await
-
同构方便,使用 isomorphic-fetch
同构(isomorphic/universal)就是使前后端运行同一套代码的意思,后端一般是指 NodeJS 环境
fetch注意点
- Fetch 请求默认是不带 cookie 的,需要设置 fetch(url, {credentials: 'include'})
- 服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject。
- 所有版本的 IE 均不支持原生 Fetch
相关引申问题
ajax、fetch、axios这三者都有什么区别?
-
ajax是一门技术,基于xmlHttpRequest。
-
fetch是一个api,基于promise,解决了xmlHttpRequest不关注分离,调用方式混乱,优化异步模型
-
axios是第三方库,基于xmlHttpRequest+promise封装
HTTP状态码
只标识出一些具有代表性的状态码:
-
200表示从客户端发来的请求在服务器端被正常处理了。 -
204表示请求处理成功,但没有资源返回。 -
301表示永久性重定向。该状态码表示请求的资源已被分配了新的URI,以后应使用资源现在所指的URI。 -
302表示临时性重定向。 -
304表示客户端发送附带条件的请求时(指采用GET方法的请求报文中包含if-matched,if-modified-since,if-none-match,if-range,if-unmodified-since任一个首部)服务器端允许请求访问资源,但因发生请求未满足条件的情况后,直接返回304Modified(服务器端资源未改变,可直接使用客户端未过期的缓存) -
400表示请求报文中存在语法错误。当错误发生时,需修改请求的内容后再次发送请求。 -
401表示未授权(Unauthorized),当前请求需要用户验证 -
403表示对请求资源的访问被服务器拒绝了 -
404表示服务器上无法找到请求的资源。除此之外,也可以在服务器端拒绝请求且不想说明理由时使用。 -
500表示服务器端在执行请求时发生了错误。也有可能是Web应用存在的bug或某些临时的故障。 -
503表示服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。