一、什么叫同步、异步
- 同步:同步操作没有结束之前,后面的代码是无法执行的。
- 异步:异步操作没有结束之前,后面的代码是可以执行的。
同步:在主线程排队的任务,只有前一个任务执行完毕才能执行后一个任务。
异步:不进入主线程,而进入“任务队列”,只有等待主线程任务执行完毕,“任务队列”开始通知主线程,请求执行任务,该任务才会进入主线程执行
二、哪些是异步问题
- 计时函数:setTimeout()、setInterval()。
- 资源加载(I/O操作):在JavaScript脚本代码中动态添加资源。
- XHR请求:利用ajax技术向服务器发出请求
三、异步问题产生的实际案例
1、计时函数产生的异步问题:
function f1(){
console.log('第一个');
}
function f2(){
setTimeout(function(){
console.log('第二个');
},0) //事件为0,整个操作也是异步的
}
function f3(){
console.log('第三个');
}
f1();
f2();
f3();
(1)ES4/ES5解决方案:使用回调函数解决
将异步操作的内容进行改造,设置一个回调函数作为参数保证同步性。
function f1(){
console.log('第一个');
}
function f2(callback){ //函数作为形参
window.setTimeout(function(){
console.log('第二个');
callback();
},2000);
}
function f3(){
console.log('第三个');
}
f1();
f2(f3); //将函数名f3传递给形参callback,callback成为了函数f3的一个引用
(2)回调函数作为参数
- 可以是一个已经定义好的函数名
- 可以是一个当时定义的匿名函数
f1();
f2(function(){
//可以书写想与f2函数的执行内容同步的任意代码
var m=100,n=1000;
console.log(m+n);
});
实际应用:jQuery中有一个方法animate({},duration,callback)
$('#ball').animate({
"margin-left":"+=200px"
},2000,function(){
//当动画结束之后执行该函数内部的代码
})
(3)当有两个异步操作是如何通过回调函数处理
function f1(){
console.log('第一个');
}
function f2(callback,callback2){
window.setTimeout(function(){
console.log('第二个');
callback(callback2);
},1000)
}
function f3(callback){
window.setTimeout(function(){
console.log('第三个');
callback();
},2000)
}
function f4(){
console.log('第四个');
}
f1();
f2(f3,f4);
结论:回调函数容易造成“回调地狱”。
2、资源加载的异步问题:
// var imgNode = document.createElement('img');
var img = new Image();
img.src = "img/a.jpg";
pic.appendChild(img);
console.log(img.width); //0
console.log(img.height); //0
解决方案:可以将输出涉及到资源的代码写在资源的onload
事件中,若资源加载失败,可以将加载失败后执行的代码写在资源的onerror
事件中。
例:
利用回调函数将加载图片的代码进行封装。
function loadImage(parent,url,success,failuer){
var img = new Image();
img.src = url;
parent.appendChild(img);
img.onload = function(){
success();
}
img.onerror = function(){
failuer();
}
}
loadImage(pic,"img/qa.jpg",function(){
console.log('图片加载成功了');
},function(){
console.log('图片加载失败了');
})
结论:回调函数可以解决异步问题。
四、ES6引入了Promise对象解决异步问题
1、语法格式:
var promise = new Promise(function(resolve,reject){
//书写异步操作的代码
if(异步操作执行成功){
resolve();
}
if(异步操作执行失败){
reject();
}
})
promise.then(function(){
//书写调用了resolve()时执行的代码
},function(){
//书写调用了reject()时执行的代码
})
2、例1:
加载一张图片,在图片加载成功后输出图片的宽度和高度,若图片加载失败则显示加载失败的提示文本。
let promise = new Promise(function(resolve,reject){
var img = new Image();
img.src = "img/a.jpg";
pic.appendChild(img);
img.onload = function(){
resolve(this);
}
img.onerroe = function(){
reject();
}
})
promise.then(function(obj){
console.log(obj)
console.log(`图片宽度=${obj.width}`);
console.log(`图片高度=${obj.height}`);
},function(){
console.log(`图片加载失败`);
})
3、例2:利用Promise
封装加载一张图片的功能。
function loadImage(parent,url){
return new Promise((resolve,reject)=>{
var img = new Image();
img.src = url;
parent.appendChild(img);
img.onload = function(){
resolve(this);
}
img.onerror = function(){
reject();
}
})
}
loadImage(pic,'img/a.jpg').then(function(obj){
console.log('图片加载成功');
console.log(obj.width);
}).catch(function(){
console.log('图片加载失败');
})
4、例3:封装confirm()
function $confirm(message){
return new Promise((resolve,reject)=>{
let temp = window.confirm(message);
if(temp){
resolve();
}else{
reject();
}
})
}
$confirm('您确定关闭吗?').then(()=>{
console.log('确认关闭');
}).catch(()=>{
console.log('取消关闭');
})
5、Promise技术解决异步问题的优势:
- 可以有多个then()连缀书写。
- Promise技术避免了回调地狱。
- 不仅给出了异步操作成功的代码解决方案,还给出了异步操作失败和完成的解决方案。
new Promise(function(resolve,reject){ //异步操作 }).then(function(){ //异步操作成功 }).catch(function(){ //异步操作失败 }).finally(function(){ //异步操作完成 })
6、例四:加载多个图片
当所有图片都加载成功后,输出“所有图片都加载成功”;有任意图片没有加载成功,输出“有图片没有加载成功”。
静态方法:Promise.all(array)
功能:返回一个Promise实例,参数array是一个元素为Promise实例的数组。当数组中所有的Promise实例均执行resolve()方法后,整个Promise实例才会表示成功。
静态方法:Promise.race(array)
功能:与Promise.all()
相反。
function loadImage(parent,url){
return new Promise((resolve,reject)=>{
let img = new Image;
img.src = url;
parent.appendChild(img);
img.onload = function(){
resolve();
}
img.onerror = function(){
reject();
}
})
}
let imgs = ['01.jpg','02.jpg','03.jpg','04.jpg','05.jpg'];
let pro = [];
for(var i=0;i<imgs.length;i++){
let temp = loadImage(box,'img/'+imgs[i]);
pro.push(temp);
}
console.log(pro);
//静态方法Promise.all()需要一个由promise组成的数组
Promise.all(pro).then(()=>{
console.log('所有图片都加载成功了');
}).catch(()=>{
console.log('有图片没有加载成功');
})
7、封装ajax技术:
- 原生JavaScript:
XMLHTTPRequest()
- jQuery:
$.ajax()
- Vue.js:
axios
- 小程序:
wx.request()
如何利用Axios
技术提交一个ajax请求:
$axios.get(url,{}).then().catch();
$axios.post(url,{}).then().catch()
问题:因为$axios
后面还要跟具体的不同两个方法(get()
、post()
),所以不能使用function进行封装。
解决:使用类封装(混合模式:属性定义在构造函数中,方法定义在原型下)。、
基本格式:
// 先创建构造函数
function Axios(){}
// 创建Axios类的方法
Axios.prototype.get=function(){
}
Axios.prototype.post=function(){
}
// 创建Axios类的实例
let $axios = new Axios();
$axios.get(); //发送get请求
$axios.post(); //发送post请求
封装ajax技术
//先创建构造函数
function Axios(){
this.xhr = new XMLHttpRequest();
}
//创建Axios类的方法
Axios.prototype.toString = function(data){
let temp = [];
for(let i in data){
temp.push(`${i}=${data[i]}`);
}
return temp.join('&');
}
Axios.prototype.get = function(url,data){
return new Promise((resolve,reject)=>{
data=data.constructor===Object?this.toString(data):data;
this.xhr.open('get',url + '?' + data);
this.xhr.send(null);
this.xhr.onreadystatechange = function(){
if(this.readyState === 4){
if(this.status === 200){
resolve(this.responseText.trim());
}
}
}
})
}
Axios.prototype.post = function(url,data){
return new Promise((resolve,reject)=>{
data=data.constructor===Object?this.toString(data):data;
this.xhr.open('post',url);
this.xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded')
this.xhr.send(data);
this.xhr.onreadystatechange = function(){
if(this.readyState === 4){
if(this.status === 200){
resolve(this.responseText.trim());
}
}
}
})
}
//创建Axios类的实例
let $axios = new Axios();
//调用Axios类的方法
btn.onclick = function(event){
event.preventDefault();
let url = 'checkGradeG.jsp';
let data = {
exno:'100100',
exname:'张三'
};
$axios.post(url,data).then(res=>{
console.log(JSON.parse(res));
})
}
//向服务器提交请求时的参数格式(jQuery的$ajax()):
//1、字符串:a=10&b=20【原生JavaScript只支持字符串】
//2、对象:{a:10,b:20}
//3、数组:[{name:'a',value:10},{name:'b',value:20}]
也可以将封装的内容和对封装的使用进行分离
例如单独写一个js文件axios.js,用的时候直接引入就行了。
(function(){
//函数自执行
//先创建构造函数
function Axios(){
this.xhr = new XMLHttpRequest();
}
//创建Axios类的方法
Axios.prototype.toString = function(data){
let temp = [];
for(let i in data){
temp.push(`${i}=${data[i]}`);
}
return temp.join('&');
}
Axios.prototype.get = function(url,data){
return new Promise((resolve,reject)=>{
data=data.constructor===Object?this.toString(data):data;
this.xhr.open('get',url + '?' + data);
this.xhr.send(null);
this.xhr.onreadystatechange = function(){
if(this.readyState === 4){
if(this.status === 200){
resolve(this.responseText.trim());
}
}
}
})
}
Axios.prototype.post = function(url,data){
return new Promise((resolve,reject)=>{
data=data.constructor===Object?this.toString(data):data;
this.xhr.open('post',url);
this.xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded')
this.xhr.send(data);
this.xhr.onreadystatechange = function(){
if(this.readyState === 4){
if(this.status === 200){
resolve(this.responseText.trim());
}
}
}
})
}
//创建Axios类的实例
//window暴露变量
window.$axios = new Axios();
})();