总结的内容:
- JS异步与单线程的关系(什么是单线程)
- event-loop事件轮询(JS异步由event-loop实现,异步是怎么执行的)
- jQuery中异步的解决方案(jQuery中的Deferred)
★JS异步与单线程的关系
JS是单线程的。
单线程:只有一个线程、同一时间只能做一件事。两端JS代码不能同时执行
原因:为了避免DOM渲染的冲突。
- 浏览器需要渲染DOM
- JS可以修改DOM
- 因此,JS的执行和浏览器渲染DOM的执行共用一个线程。JS执行的时候,浏览器渲染DOM会暂停。(都修改DOM就冲突了)
- webworker支持多线程,但是不能访问DOM
单线程的解决方案:异步[async/await、promise解决方案](虽然也有问题)
异步解决:
异步解决单线程的问题: - 没按照书写顺序执行,可读性差
- callback中不容易模块化
★event-loop事件轮询
异步的实现方式event-loop
- 事件轮询,JS实现异步的具体方案
- 同步代码直接执行
- 异步函数先放入异步队列中
- 待同步函数执行完毕,轮询执行 异步队列 中的函数
实例分析一:
等主进程中的函数执行完,将异步队列中的函数放入主进程中执行
实例分析二:
事件轮询执行步骤:
- 主进程执行到第一个setTimeout,看是个异步,等100ms之后放入异步队列中
- 下来执行到第二个setTimeout,是个异步,直接放入异步队列中
- 主进程直接执行console.log(3);
- 同步代码执行完成后看异步队列中有没有函数
- 这时异步队列中只有第二个setTimeout函数
- 将第二个setTimeout中的函数拿到主进程中执行
- 立即到异步队列中看看还有没有要执行的函数
- JS引擎会轮询着来看异步队列中有没有
- 100ms之后,第一个setTimeout中的函数放入异步队列
- JS引擎会立刻将这个函数放入主进程中执行
事件轮询event-loop像一个哨兵一样,监视异步队列,只要有函数进入异步队列,立刻将函数拿到主进程中执行,立即又回去监视着
实例分析三
ajax加载完成时放入异步队列
所以实例分析三的结果可能是dcba 也可能是dcab
event-loop总结:
- 同步代码,直接执行
- 异步函数先放入异步队列中【异步定时0ms直接放入异步队列,异步定时100ms是100ms之后放入异步队列,ajax请求成功后将callback函数放入异步队列】
- 待同步函数执行完毕,轮询执行异步队列的函数
★ jQuery中异步的解决方案
jQuery1.5的变化
jQuery1.5之前ajax请求写法:
var ajax=$.ajax({
url:"data.json",
success:function(){
console.log("success1");
console.log("success2");
console.log("success3");
},
error:function(){
console.log(error);
}
})
console.log(ajax); //返回一个XHR对象
对修改开放,对扩展封闭,不满足开放封闭原则
jQuery1.5之后ajax请求写法:
var ajax=$.ajax("data.json");
ajax.done(function(){
console.log("success1");
}).fail(function(){
console.log("error");
}).done(function(){
console.log("success2");
})
console.log(ajax); //返回一个deferred对象
到最后:很像promise的写法
var ajax=$.ajax("data.json");
ajax.then(function(){
//成功回调
},function(){
//失败回调
}).then(function(){
//成功回调
},function(){
//失败回调
})
与promise相似,所以promise是先从jquery开始的。.fail .done .then这种形式是通过jQuery.deferred实现的
对修改封闭,对扩展开放,满足开放封闭原则
jQuery1.5的变化
- 无法改变JS单线程的本质
- 只能从写法上杜绝callback这种形式
- 它是一种语法糖形式,但是解耦了代码
- 很好的体现了:开放封闭原则(多人开发有质的提高,有错误时好查,极大地减少了有错误时排查错误的能力)
jQuery Deferred的使用:
var wait=function(){
var task=function(){
console.log("执行完成");
}
setTimeout(task,2000);
}
wait();
//新增需求,要在执行完成之后进行某些特别复杂的操作,代码可能会很多,而且分好几个步骤。
//可以直接在console.log("执行完成")后面写,但不符合开放封闭原则,所以不能这样写,需要使用jQuery Deferred
使用jQuery Deferred:
function waitHandle() {
var dtd = $.Deferred(); //创建一个deferred对象
var wait = function (dtd) { //要求传入一个deferred对象
var task = function (){
console.log("执行完成");
dtd.resolve(); //表示异步任务已经完成
//dtd.reject(); //表示异步任务失败或是出错
}
setTimeout(task,2000);
return dtd //要求返回deferred对象
}
//注意,这里一定要有返回值
return wait(dtd);
}
var w=waitHandle(); //deferred对象
w.then(function(){
console.log("success1");
},function(){
console.log("error1");
}).then(function(){
console.log("success2");
},function(){
console.log("error2");
})
多个.then,对扩展开放,对修改封闭,满足开放封闭原则,若都写在console.log(“执行完成”)后面,则不满足开放封闭原则。
Deferred总结:
dtd的API可分为两类:
- dtd.resolve dtd.reject (系统主动执行)
- dtd.then(监听) dtd.done dtd.fail (被动调用)
dtd.then包含两个参数,若成功,执行第一个参数,即dtd.resolve。若失败,执行第二个参数,即dtd.reject。
注意:这两类API不能混合使用
jQuery1.5之后,wait函数返回的是dtd.promise()。
function waitHandle() {
var dtd = $.Deferred(); //创建一个deferred对象
var wait = function (dtd) { //要求传入一个deferred对象
var task = function (){
console.log("执行完成");
dtd.resolve(); //表示异步任务已经完成
//dtd.reject(); //表示异步任务失败或是出错
}
setTimeout(task,2000);
return dtd.promise() //要求返回deferred对象
};
//注意,这里一定要有返回值
return wait(dtd);
}
var w=waitHandle(); //经过上面的改动,w接收的是一个promise对象
w.then(function(){
console.log("success1");
},function(){
console.log("error1");
}).then(function(){
console.log("success2");
},function(){
console.log("error2");
});
w.reject(); //执行这句话直接会报错 因为返回promise对象,则w只有.then .done .fail这种被动监听的方法,把dtd的reject resolve给过滤掉了,外面不能再调用,只有我封装的时候才可以调用。
(promise和deferred的对比)deferred可以主动调用reject和resolve方法,所以有可能被别人篡改代码,而promise只能调用.then .done .fail这种被动监听的方法,别人不能修改,比较安全,也满足开放封闭原则。
(promise和deferred的区别)Deferred实例既可以执行.resolve .reject这种主动执行的函数,又可以执行.then .fail .down这种被动监听结果的函数,但这两类放在一起会发生冲突,又封装出了Promise对象,Promise只能去被动监听.done .then .fail这种结果,不能主动干预失败成功的操作,完美解决了问题