Ajax
什么是Ajax?Ajax(Asynchronous JavaScript and XML)是一门技术,可以实现在不刷新页面的情况下向服务器端发送请求,从而更新数据。
回调函数
什么函数才是回调函数?自己定义的自己却没有调用但最终它执行了。比如将一个函数作为参数传递给另外一个函数,那作为参数的这个函数就是回调函数。
常见的回调函数?1>dom事件回调函数2>定时器回调函数 3>Ajax请求回调函数(后台交互) 4>生命周期回调函数
回调分为同步回调和异步回调。
同步回调是回调函数在主函数结束之前调用;异步回调是回调函数在主函数执行结束之后调用。
异步回调的两种方式:
第一种是把异步回调函数封装成一个宏任务,添加到消息队列尾部,当循环系统执行到该任务的时候执行回调函数(前面介绍的 setTimeout 和 XMLHttpRequest 的回调函数都是通过这种方式来实现的);第二种方式是微任务,执行时机是在主函数执行结束之后、当前宏任务结束之前执行回调函数(MutationObserver和Promise)。
当循环系统在执行一个任务的时候,都要为这个任务维护一个系统调用栈。这个系统调用栈类似于 JavaScript 的调用栈。(消息队列里的每一个任务,每一个任务都维护一个栈执行子任务)
XHR
XMLHttpRequest运作机制:
步骤
第一步:创建 XMLHttpRequest 对象。
当执行到var xhr = new XMLHttpRequest()
后,JavaScript 引擎线程会创建一个 XMLHttpRequest 对象 xhr,用来执行实际的网络请求操作。
第二步:为 xhr 对象注册回调函数。
(因为网络请求比较耗时,所以要注册回调函数,这样后台任务执行完成之后就会通过调用回调函数来告诉其执行结果。XMLHttpRequest 的回调函数主要有下面几种:ontimeout,用来监控超时请求,如果后台请求超时了,该函数会被调用;onerror,用来监控出错信息,如果后台请求出错了,该函数会被调用;onreadystatechange,用来监控后台请求过程中的状态,比如可以监控到 HTTP 头加载完成的消息、HTTP 响应体消息以及数据加载完成的消息等。)
xhr.onreadystatechange=function(){
if(xhr.readyState===4){
if (xhr.status === 200) { // 服务端 状态码
var responseText=JSON.parse(xhr.responseText);
var str='<h2>'+responseText.name+'</h2>';
document.body.innerHTML=str;
}else{
console.error(xhr.statusText); //状态码的文本描述,如200的statusText是ok
}
}
}
第三步:配置基础的请求信息。
首先要通过 open 接口配置一些基础的请求信息,包括请求的地址、请求方法(是 get 还是 post)、请求参数(与地址用?分隔。参数名=参数值。GET放在请求地址后面,POST放在send(params)方法里)。
然后通过 xhr 内部属性类配置一些其他可选的请求信息,如通过xhr.timeout = 3000来配置超时时间,也就是说如果请求超过 3000 毫秒还没有响应,那么这次请求就被判断为失败了。
还可以通过xhr.responseType = "text"来配置服务器返回的格式,将服务器返回的数据自动转换为自己想要的格式,如果将 responseType 的值设置为 json,那么系统会自动将服务器返回的数据转换为 JavaScript 对象格式。
xhr.open('GET', 'https://bbin.com'); // 初始化请求参数,还没发送请求 true表示异步
第四步:发起请求。
一切准备就绪之后,就可以调用xhr.send()来发起网络请求了。你可以对照上面那张请求流程图,可以看到:渲染进程会将请求发送给网络进程,然后网络进程负责资源的下载,等网络进程接收到数据之后,就会利用 IPC 来通知渲染进程;渲染进程接收到消息之后(此时在IO线程上),会将 xhr 的回调函数封装成任务并添加到消息队列中,等主线程循环系统执行到该任务的时候,就会根据相关的状态来调用对应的回调函数。
- 如果网络请求出错了,就会执行 xhr.onerror;
- 如果超时了,就会执行 xhr.ontimeout;
- 如果是正常的数据接收,就会执行onreadystatechange 来反馈相应的状态。
xhr.send(); // 向服务器发送请求.
//对于POST请求,这里改为xhr.send(params);在前面还多了一行设置请求头属性的类
//xhr.setRequestHeader(‘Content-Type’,‘application/x-www-form-urlencoded’);
readyState的几个值:
从 0 到 4 发生变化。
● 0: 请求未初始化,尚未调用open()方法
● 1: 服务器连接已建立,open()方法已被调用
● 2: 请求已接收,send()方法已被调用,并且头部和状态已经可获得
● 3: 请求处理中,下载中且responseText属性已经包含部分数据
● 4: 请求已完成,且响应已就绪
GET请求不能提交JSON对象数据格式,传统网站的表单提交也是不支持JSON对象数据格式的。POST可以。
发起Ajax请求的过程,浏览器底层做了什么:
预备知识:
浏览器内核中的多线程浏览器中的页面循环系统 事件循环:js引擎线程会从任务队列中读取事件并执行,这种运行机制称作 Event Loop。
- 对于一个AJAX请求,js引擎线程先生成 XMLHttpRequest实例对象,open、send。目前所有的语句都是同步执行。
- send之后,浏览器为这个网络请求创建了新的http请求线程,这个线程独立于js线程,两者异步工作。渲染进程会将请求发送给网络进程,然后网络进程负责资源的下载,等网络进程接收到数据之后,就会利用 IPC 来通知渲染进程;渲染进程接收到消息之后(此时在IO线程上),会将 xhr 的回调函数封装成任务并添加到消息队列中,等主线程循环系统执行到该任务的时候,就会根据相关的状态来调用对应的回调函数。
- ajax请求被服务器响应后,浏览器事件触发线程捕获到了ajax的回调事件(onreadystatechange、onload、onerror等),该回调事件并没有马上被执行,而是被添加到任务队列(依次执行)的末尾,等待轮到被执行。(这个时候js还在异步地依次处理任务队列中前面的请求。)
- 在回调事件中,有可能会对DOM进行操作,这个时候浏览器便会挂起js引擎线程,转到GUI渲染线程,进行UI重绘或者回流。当GUI渲染线程的任务完成时,浏览器将会挂起该线程,然后激活js引擎线程。(js线程和GUI渲染线程是互斥的,因为怕有冲突)
整个ajax请求过程中,除了GUI线程和js引擎线程是互斥的,其他的线程之间都可以并行执行,因此ajax并没有破坏js的单线程机制。
在使用 XMLHttpRequest 的过程中“两坑”:跨域问题和HTTPS混合内容问题。
手写Ajax
function ajax(defaults ){
let xhr=new XMLHttpRequest();
let params='';
for(let key in defaults.data){
params=params+key+'='+defaults.data[key]+'&';
}
params=params.substr(0,params.length-1);
if(defaults.method=='get'){
defaults.url+='?'+params;
}
xhr.open(defaults.method,defaults.url);
if(defaults.type=='post'){
var contentType=defaults.header['Content-Type']
xhr.setRequestHeader('Content-Type',contentType);
if(contentType=='application/json'){
xhr.send(JSON.stringify(defaults.data));
}
else{
xhr.send(params);
}
}
else{
xhr.send();
}
xhr.onload=function(){
var contentType=xhr.getResponseHeader('Content-Type');
var responseText=xhr.responseText;
//判断服务器返回的数据类型是否是json
if(contentType.includes('application/json')){
//将json字符串转换为json对象
responseText=JSON.parse(responseText);
}
if(xhr.status==200){
defaults.succcess(responseText,xhr);
}else{
defaults.error(responseText,xhr);
}
}
}
ajax({
type:'get',
url:'http://example.com',
data:{
name:'zhangsan',
age:18
},
header:{
//'Content-Type':'application/x-www-form-urlencoded'
'Content-Type':'application/json'
},
success:function(data,xhr){
//一些对数据想做的处理
console.log(data);
},
error:function(data,xhr){
}
})
简化版:
//method,params{key:value},url
function ajax(obj){
let xhr=new xmlHttpRequest();
let method='get'||obj.method;
let params='';
for(let key in obj.params){
params=params+key+'='+obj.params[key]+'&';
}
let url=obj.url+'?'+params;
if(method==='get'){
xhr.open(method,url);
xhr.send();
}else if(method==='post'){
xhr.open(method,obj.url);
xhr.send(params);
}
xhr.onReadyStateChange=function(){
if(xhr.readyState===4){
if(xhr.status===200){
obj.success(JSON.parse(xhr.responseText));
}else{
obj.fail();
}
}
}
}
基于Promise处理Ajax请求:
function queryData(url){
return new Promise(function(resolve,reject){
var xhr=new XMLHttpRequest();
xhr.onreadystatechange=function(){
if(xhr.readyState==4&&xhr.status==200){
//处理正常的情况
resolve(xhr.responseText);
}
else{
reject('服务器错误');
}
};
xhr.open('get',url);
xhr.send(null);
});
}
p=queryData('http://localhost:3000/data');
p.then(function(data){
console.log(data);
}.catch(function(err){
console.log(err);
})
Axios:
什么是Axios?Axios是网络请求封装库,本质上是通过Promise对xhr的封装。相比于原生的XMLHttpRequest对象,axios简单易用。
特征:
- 支持浏览器和nodejs(浏览器中创建xhr,nodejs中创建http请求);
- 支持promise语法;
- 能拦截请求和响应;
- 自动转换JSON数据
- 支持中断请求
- 支持请求进度检测
- 客户端支持防御 XSRF
axios的响应结果:响应结果的主要属性:data:实际响应回来的数据;headers:响应头信息;status:响应状态码;statusText:响应状态信息。
Fetch:
fetch是浏览器的一个api。是另外一种请求数据的方式,也可以发送HTTP请求,是一个基于promise语法的函数。
fetch(url)返回一个promise
fetch(url).then(response=>{console.log(response.json())}) 打印出一个response对象
fetch(url).then(response=>{console.log(response.json())}) .then(data=>{console.log(data)})
fetch有两个参数,第一个参数是url,第二个参数是一个对象,对象里有个method属性,默认fetch是GET方法,改为POST就是POST方法了。还有body、headers属性。
fetch的缺点:
- fetch默认不带cookie,需要添加配置项:fetch(url,{credentials:‘include’});
- fetch不支持请求中断
- fetch没办法检测原生请求的进度(如文件上传进度条)
Ajax、Fetch、axios三者之间的关系
Axios和Fetch的区别:
axios是对xhr的封装,但是fetch与xhr没关系,即不用借助xhr就能发送请求。
三者关系:
ajax是js异步技术的术语,早起相关的api是xhr,它是一个术语。
fetch是es6新增的用于网络请求标准api,它是一个api。
axios是用于网络请求的第三方库,它是一个库。
FormData对象:
作用:①模拟HTML表单,相当于将HTML表单映射成表单对象,自动将表单对象中的数据拼接成请求参数的格式。②异步上传二进制文件(图片、视频、音频)。
var form=document.getElementById('form');//获取表单
var formData=new FormData(form);
xhr.send(formData);//只能用于POST方法
//也可以先创建一个空的formData对象,再用append方法添加属性
var formData=new FormData();
FormData对象的实例方法:get() set()会覆盖重复的属性 delete() append()不会覆盖重复的属性。