Ajax的学习
- 什么是Ajax
- ajax全称是
async javascript + xml
即 异步JavaScript加xml技术 - 特点:实现页面
动态刷新
- 优点:可以
无需刷新页面
与服务端进行通信,可允许根据用户事件来更新部分页面
- 缺点:
没有浏览历史,不能后退
。存在跨域
问题
- ajax全称是
- 什么是XML
- 可扩展标记语言,被设计用来传输以及存储数据
- xml和html类似,不同的是html是预定义标签而xml中没有预定义标签,全都是自定义标签,用来表示一些数据
http请求
- 请求报文:格式
- 请求行:请求类型 请求参数 http版本号
- 请求头:Host: xxx 等
- 空格
- 请求体
- 响应报文
- 响应行: http版本号 状态码 响应状态字符串
- 响应头
- 空格
- 请求体
XHR对象
- xhr对象:XMLHttpRequest对象
- 它的使用方法:
- 创建对象:
let xhr = new XMLHttpRequest()
- 初始化:
xhr.open('请求方式','请求地址','请求是否异步')
- 发送:
xhr.send(null)
发送请求,如果没有请求体的话,send内必须参数为null,有的话,send内为请求体内容 - 事件绑定:接受并处理服务端返回的结果
xhr.onreadystatechange=function(){}
为保证跨浏览器的兼容性,onreadystatechange
要在open
之前调用、
- 创建对象:
- 如果接受到响应数据后,xhr对象的下列属性会被赋值
responseText
: 作为响应体返回的位文本responseXml
: 如果响应的内容类型是text/xml或者application/xml时,就是包含响应数据的xml dom文档status
: 响应的http状态statusText
:响应的http状态描述
- 在异步情况下:我们不应阻塞代码运行,xhr对象给我们准备了一个
readyState
属性:表示当前处于请求的哪个阶段。我们可以通过监听readyState改变触发的readystatechange
事件处理程序- 0:未初始化(uninitialized)(尚未调用open方法)
- 1:
已打开(open)
,已调用open,但是没有调用send - 2:
已发送(send)
,已发送但是并未接收响应 - 3:
接收中(receiving)
已经收到部分响应 - 4:
完成(complete)
已接收到所有响应
xhr.onreadystatechange = function(){ if(xhr.readyState == 4){ if(xhr.status){ // 根据status(响应状态码,处理响应逻辑) } } }
- 完整的一个ajax请求
var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(){ if(xhr.readtState==4){ if(xhr.status==200){ //成功接收到数据 } } } xhr.open('get','http:xxxx',true) // get请求,参数可以直接拼接到url上,post参数在send内 xhr.send(null)
xhr.setRequestHeader :自定义请求头内容
- xhr发送请求默认会有下面头部字段
Accept
:浏览器可以处理的内容类型Accept-Charset
:浏览器可以显示的字符集Accept-Encoding
:浏览器可以处理的压缩编码类型Accept-Language
:浏览器使用的语言Connection
:浏览器与服务器的连接类型Cookie
:页面中设置的cookieHost
: 发送请求的页面所在的域Referer
:发送请求的页面的uri
XHR超时
xhr.timeout
请求超时时间xhr.ontimeout = function(){}
:请求超时的回调- 请求超时之后,
readtState
也会变成4
,也会调用onreadrstatechange
事件,不过访问xhr.status
属性会报错
,此时可以try catch语句
包裹onreadrstatechange
事件
XHR进度事件
loadstart
:在接收到响应的第一个字节时触发progress
: 在接收响应期间来回触发:该方法接收一个对象参数event,该参数有三个属性 lengthComputable表示进度是否可用,position接收到的字节数,totalSize响应头定义的总字节数error
:在请求出错时触发abort
:在调用abort() 终止连接时触发load
: 在成功接收完响应时触发loadend
: 通讯完成时,且在error abort load之后触发
jquery中的ajax
- 对js原生的Ajax进行了封装,在封装后的Ajax的操作更加简洁,功能更加强大
- jQuery中常见的ajax请求
- $.get(url, [data], [callback], [type])
// url:待载入页面的URL地址 // data:待发送 Key/value 参数。 // callback:载入成功时回调函数。 // type:返回内容格式,xml, html, script, json, text, _default。 $.get("test.cgi", { name: "John", time: "2pm" }, function(data){ alert("Data Loaded: " + data); });
- $.post(url, [data], [callback], [type])
// url:待载入页面的URL地址 // data:待发送 Key/value 参数。 // callback:载入成功时回调函数。 // type:返回内容格式,xml, html, script, json, text, _default。 $.post("test.php", { "func": "getNameAndTime" }, function(data){ alert(data.name); // John console.log(data.time); // 2pm }, "json");
- $.ajax(url,[settings]),返回其创建的
XMLHttpRequest
对象。大多数情况下你无需直接操作该函数,除非你需要操作不常用的选项,以获得更多的灵活性。jQuery.ajax
// 简单的使用方法如下:其他具体配置请查看官网 $.ajax({ type: "POST", url: "some.php", data: "name=John&location=Boston", success: function(msg){ alert( "Data Saved: " + msg ); } });
- $.getScript(url,[callback]) 通过 HTTP GET 请求载入并执行一个 JavaScript 文件。
jQuery.getScript("https://dev.jquery.com/view/trunk/plugins/color/jquery.color.js", function(){ $("#go").click(function(){ $(".block").animate( { backgroundColor: 'pink' }, 1000) .animate( { backgroundColor: 'blue' }, 1000); }); });
- $.getJSON(url,[data],[fn]) 通过 HTTP GET 请求载入 JSON 数据。
$.getJSON("test.js", { name: "John", time: "2pm" }, function(json){ alert("JSON Data: " + json.users[3].name); });
fetch
- fetch()是 XMLHttpRequest 的升级版,用于在 JavaScript 脚本里面发出 HTTP 请求。浏览器原生提供这个对象
- fetch()的功能与 XMLHttpRequest 基本相同,但有三个主要的差异
- fetch()使用 Promise,不使用回调函数
- fetch()采用模块化设计,API 分散在多个对象上(Response 对象、Request 对象、Headers 对象)
- fetch()通过数据流(Stream 对象)处理数据,就是fetch返回回来的response对象是一个Stream 对象,需要通过response.json()是一个异步操作,取出所有内容,并将其转为 JSON 对象。
- Response 对象:处理 HTTP 回应
- fetch()请求成功以后,得到的是一个 Response 对象。它对应服务器的 HTTP 回应。如果想要通过response对象包含的数据需要通过Stream 接口异步读取,但是对于response包含的同步属性,可以直接读取(HTTP回应的headers)
// Response.ok 属性返回一个布尔值,表示请求是否成功 // Response.status 属性返回一个数字 表示 HTTP 回应的状态码 // Response.statusText 属性返回一个字符串,表示 HTTP 回应的状态信息 // Response.url 属性返回请求的 URL。如果 URL 存在跳转,该属性返回的是最终 URL。 // Response.type 属性返回请求的类型:basic,cors,error等,这里不是get/post // Response.redirected 属性返回一个布尔值,表示请求是否发生过跳转。 async function fetchText() { let response = await fetch('/readme.txt'); console.log(response.status); console.log(response.statusText); } // response.text():得到文本字符串。 // response.json():得到 JSON 对象。 // response.blob():得到二进制 Blob 对象。 // response.formData():得到 FormData 表单对象。 // response.arrayBuffer():得到二进制 ArrayBuffer 对象。
- fetch的语法
// fetch()的第一个参数是 URL,还可以接受第二个参数,作为配置对象,定制发出的 HTTP 请求。 fetch(url) fetch(url, optionObj) fetch(url, { method: "GET", headers: { "Content-Type": "text/plain;charset=UTF-8" }, body: undefined, referrer: "about:client", // 属性用于设定fetch()请求的referer标头。 referrerPolicy: "no-referrer-when-downgrade", // 属性用于设定Referer标头的规则 mode: "cors", // 属性指定请求的模式。 credentials: "same-origin", // 属性指定是否发送 Cookie。 cache: "default", // 属性指定如何处理缓存 redirect: "follow", // 属性指定 HTTP 跳转的处理方法。 integrity: "", // 属性指定一个哈希值,用于检查 HTTP 回应传回的数据是否等于这个预先设定的哈希值。 keepalive: false, // 属性用于页面卸载时,告诉浏览器在后台保持连接,继续发送数 signal: undefined //属性指定一个 AbortSignal 实例 });
- fetch()请求的底层用的是 Request() 对象的接口,参数完全一样,因此上面的 API 也是Request()的 API。所以上面的optionObj 可以是 new Request()的实例对象
- fetch()请求发送以后,如果中途想要取消,需要使用AbortController对象
let controller = new AbortController(); setTimeout(() => controller.abort(), 1000); try { let response = await fetch('/long-operation', { signal: controller.signal }); } catch(err) { if (err.name == 'AbortError') { console.log('Aborted!'); } else { throw err; } }
axios
- axios特点
- 基于
promise
的异步ajax请求 - 浏览器端/
node端
都可以使用 - 支持请求/响应
拦截器
- 支持请求
取消
- 请求/响应
数据转换
批量
发送多个请求
- 基于
- axios常用语法
-
axios(config)
:通用的/最本质的发任意类型请求的方式 -
axios(url[,config])
:可以指定url发送get请求 -
axios.request(config)
: 等同于axios(config) -
axios.get(url[,config])
: 发get请求 -
axios.delete(url[,config])
: 发delete请求 -
axios.post(url[,config])
: 发post请求 -
axios.put(url[,config])
: 发put请求 -
axios.defaults.xxxx
请求的默认全局配置 -
axios.interceptors.request.use()
添加请求拦截器 -
axios.interceptors.response.use()
添加响应拦截器 -
axios.create([config])
创建一个新的axios,下面几个方法通过create创建的实例不能使用 -
axios.Cancel
:用于创建取消请求的错误对象 -
axios.CancelToken
:用于创建取消请求的token对象 -
axios.isCancel
: 是否是一个取消请求的错误,在error
的时候判断是正常的请求失败还是说通过Cancel
创建的cancel
错误对象 -
axios.all
:用于批量执行多个异步请求 -
axios.spread
:用来收所有成功数据的回调函数的方法
-
axios中的重点方法
axios.create
当有多个后端端口时每个端口时如果单纯使用axios太麻烦使用axios.create就非常的方便axios.interceptors.request.use()
请求拦截axios.interceptors.response.use()
响应拦截axios.CancelToken;
取消请求:在config的配置里面新增一个cancelToken属性,该属性使用如下const CancelToken = axios.CancelToken; let cancel; axios.get('/user/12345', { cancelToken: new CancelToken(function executor(c) { // executor 函数接收一个 cancel 函数作为参数 cancel = c; }) }); // cancel the request cancel("强制请求失败"); // 该参数可以通过在promise中的error获取到,不过是一个Cancel对象
关于axios插件的分析
-
axios为什么既可以函数调用,还能当作对象获取属性,并且它与Axios构造函数的关系
- 首先源码里面通过有Axios构造函数
function Axios(instanceConfig) { this.defaults = instanceConfig; this.interceptors = { request: new InterceptorManager(), response: new InterceptorManager() }; } // Axios原型上的request方法,所有的请求都是通过该方法发出的,axios.get/axios.post等也是通过该方法传递method为get/post请求的 Axios.prototype.request = function request(configOrUrl, config) { } // 下面通过createInstance构造方法创建一个content(Axios实例) function createInstance(defaultConfig) { var context = new Axios(defaultConfig); // 通过bind方法将Axios.prototype,request方法,绑定到context上,返回一个新函数赋值给instance var instance = bind(Axios.prototype.request, context); // Copy axios.prototype to instance // 拷贝axios.prototype原型上的方法到instance上 utils.extend(instance, Axios.prototype, context); // 拷贝Axios实例context属性到instance // Copy context to instance utils.extend(instance, context); // 通过以上步骤,axios就能当函数使用,也能当对象使用 // 从语法上说axios不是Axios的实例 // 但通过以上方式它又有Axios的原型方法以及属性,所以从功能上来说,axios是Axios的实例 // Factory for creating new instances instance.create = function create(instanceConfig) { return createInstance(mergeConfig(defaultConfig, instanceConfig)); }; return instance; } // Expose Axios class to allow class inheritance axios.Axios = Axios; // Expose Cancel & CancelToken axios.CanceledError = require('./cancel/CanceledError'); axios.CancelToken = require('./cancel/CancelToken'); axios.isCancel = require('./cancel/isCancel'); axios.VERSION = require('./env/data').version; // Expose AxiosError class axios.AxiosError = require('../lib/core/AxiosError'); // alias for CanceledError for backward compatibility axios.Cancel = axios.CanceledError; // Expose all/spread axios.all = function all(promises) { return Promise.all(promises); }; axios.spread = require('./helpers/spread'); // Expose isAxiosError axios.isAxiosError = require('./helpers/isAxiosError'); module.exports = axios;
- 首先源码里面通过有Axios构造函数
-
axios
与axios.create
创建出来的实例的区别- 通过上面代码可以看到
axios
创建出来之后,会有其他方法以及属性的挂载
,而axios.create
创建出来的实例没有
其他方法的挂载 - 他们都是
通过createInstance
构造函数创建出来的
- 通过上面代码可以看到
-
axios()
函数以及axios.request()
以及其他请求axios.get()/axios.post()
的关系- axios通过
createInstance
构造函数创建出来之后,在通过bind(Axios.prototype.request, context)
方式让其可以使用request方法,所以说axios方法执行和Axios.prototype.request方法执行是同一个方法
axios.get()/axios.post()
通过下面代码可知,get以及post方式请求内部也是调用了Axios.prototype.request
方法utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) { /*eslint func-names:0*/ Axios.prototype[method] = function(url, config) { return this.request(mergeConfig(config || {}, { method: method, url: url, data: (config || {}).data })); }; }); utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) { /*eslint func-names:0*/ Axios.prototype[method] = function(url, data, config) { return this.request(mergeConfig(config || {}, { method: method, url: url, data: data })); };
- axios通过
-
axios运行的整体流程
-
axios中最重要的三个函数
request
串联整个流程- 这个方法中最重要的是
requestInterceptorChain
请求拦截器数组,responseInterceptorChain
响应拦截器数据,chain[dispatchRequest, undefined]
这三个数组 - 在未串联流程之前,创建了一个
resolve
状态的promise(config)
- 代码中通过
unshift
以及concat
方法把请求拦截器数组,chain,响应拦截器数组
组合一起,这样就把请求拦截,请求,响应拦截串联一起,通过promise.then方法进行流程 请求拦截器
的执行过程是后添加先执行(unshift)
,而响应拦截器``的先后是先添加先执行(concat)
- 然后
while
循环调用then
方法,最终返回
一个``最终结果的promise
Axios.prototype.request = function request(configOrUrl, config) { /*eslint no-param-reassign:0*/ // Allow for axios('example/url'[, config]) a la fetch API if (typeof configOrUrl === 'string') { config = config || {}; config.url = configOrUrl; } else { config = configOrUrl || {}; } config = mergeConfig(this.defaults, config); // Set 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'; } var transitional = config.transitional; if (transitional !== undefined) { validator.assertOptions(transitional, { silentJSONParsing: validators.transitional(validators.boolean), forcedJSONParsing: validators.transitional(validators.boolean), clarifyTimeoutError: validators.transitional(validators.boolean) }, false); } // filter out skipped interceptors var requestInterceptorChain = []; var synchronousRequestInterceptors = true; this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) { return; } synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous; requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected); }); var responseInterceptorChain = []; this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected); }); var promise; if (!synchronousRequestInterceptors) { var chain = [dispatchRequest, undefined]; Array.prototype.unshift.apply(chain, requestInterceptorChain); chain = chain.concat(responseInterceptorChain); promise = Promise.resolve(config); while (chain.length) { promise = promise.then(chain.shift(), chain.shift()); } return promise; } var newConfig = config; while (requestInterceptorChain.length) { var onFulfilled = requestInterceptorChain.shift(); var onRejected = requestInterceptorChain.shift(); try { newConfig = onFulfilled(newConfig); } catch (error) { onRejected(error); break; } } try { promise = dispatchRequest(newConfig); } catch (error) { return Promise.reject(error); } while (responseInterceptorChain.length) { promise = promise.then(responseInterceptorChain.shift(), responseInterceptorChain.shift()); } return promise; };
- 这个方法中最重要的是
- dispatchRequest:数据处理函数,该函数是
在request的循环
过程中执行
的在该函数中首先处理请求参数
,然后返回一个return adapter(config).then(function onAdapterResolution(response) {}函数
,该函数中进行响应数据处理
,而adapter
就是封装XMLRequest
请求对象的函数module.exports = function dispatchRequest(config) { //处理请求参数对象 // Transform request data config.data = transformData.call( config, config.data, config.headers, config.transformRequest ); var adapter = config.adapter || defaults.adapter; return adapter(config).then(function onAdapterResolution(response) { // Transform response data // 处理响应数据 response.data = transformData.call( config, 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.call( config, reason.response.data, reason.response.headers, config.transformResponse ); } } return Promise.reject(reason); }); };
- xhrAdaper:实际就是xhr对象的封装
-
以上就是axios过程的整体流程
同源策略
- 所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个 ip 地址,也非同源
- 同源策略即:不同源之间的页面,不准互相访问数据。
解决跨域的方式
jsonp:通过script标签能够跨域的特性,实现请求的跨域
cros解决跨域,通过后端配置实现cros
- CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。
- 整个CORS通信过程,都是浏览器自动完成,不需要用户参与。
- 实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
- 浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。浏览器对这两种请求的处理,是不一样的
- 对于简单请求,浏览器直接发出CORS请求,在头信息之中,增加一个Origin字段。Origin字段来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。
- 如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段,就知道出错了
- 如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。
Access-Control-Allow-Origin: xxx Access-Control-Allow-Credentials: true Access-Control-Expose-Headers: xxxx
- Access-Control-Allow-Origin:该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求
- Access-Control-Allow-Credentials:该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。
- Access-Control-Expose-Headers:该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader(‘FooBar’)可以返回FooBar字段的值。
- 非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。
- 浏览器发现,这是一个非简单请求,就自动发出一个"预检"请求,要求服务器确认可以这样请求。下面是这个"预检"请求的HTTP头信息。
OPTIONS /cors HTTP/1.1 // "预检"请求用的请求方法是OPTIONS,表示这个请求是用来询问的 Origin: http://api.bob.com Access-Control-Request-Method: PUT // 该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法 Access-Control-Request-Headers: X-Custom-Header // 该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是X-Custom-Header。 Host: api.alice.com Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0....
- 服务器收到"预检"请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应
HTTP/1.1 200 OK Date: Mon, 01 Dec 2008 01:15:39 GMT Server: Apache/2.0.61 (Unix) Access-Control-Allow-Origin: http://api.bob.com //表示http://api.bob.com可以请求数据。该字段也可以设为星号,表示同意任意跨源请求。 Access-Control-Allow-Methods: GET, POST, PUT // 表明服务器支持的所有跨域请求的方法 Access-Control-Allow-Headers: X-Custom-Header // 它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。 Content-Type: text/html; charset=utf-8 Content-Encoding: gzip Content-Length: 0 Keep-Alive: timeout=2, max=100 Connection: Keep-Alive Content-Type: text/plain // Access-Control-Max-Age 用来指定本次预检请求的有效期,