javascript 语言的执行环境是‘单线程’的,就是指一次只能完成一个任务。如果有多个任务,就必须排队执行,前面的任务完成后再执行下一个任务。那么为什么是单线程的呢?因为js渲染在浏览器上,包含了许多与用户的交互,如果是多线程那么试想一个场景:一个线程在某个dom上添加内容,而另一个线程删除这个dom,那么浏览器要如何反应呢?这就乱套了。
这种环境的好处就是实现起来比较简单,执行环境相对单纯;坏处就是只要有一个耗时较长的任务,后面的任务都必须排队等着。会脱氨整个程序的执行。常见的浏览器无响应。往往就是因为某一段javascript代码长时间执行,比如遇到一个死循环,就会导致整个页面卡在这个地方。其他任务无法执行。
单线程下所有的任务都是需要排队的,而这些任务分为两种:同步以及异步。
同步:可以理解为在执行完一个函数或方法之后,一直等待系统返回值或消息,这时程序是出于阻塞的,只有接收到返回的值或消息后才往下执行其他的命令。
异步:就是代码执行的顺序并不是按照从上到下的顺序一次性一次执行,而是在不同的时间段执行,一部分代码在“未来执行”。
下面说明几种常见的处理异步的方法
如本文解释不清 可以参考 阮大神的文章 下面附链接
http://www.ruanyifeng.com/blog/2012/12/asynchronous%EF%BC%BFjavascript.html
回调时一个函数被作为一个参数传递到另一个函数里面,在那个函数执行完后再执行。(也就是:B函数被作为参数传进A函数,在A函数执行完成以后再去执行B函数)
举个通俗的例子
你有事去隔壁寝室找同学,发现人不在,你怎么办呢?
方法1,每隔几分钟再去趟隔壁寝室,看人在不
方法2,拜托与他同寝室的人,看到他回来时叫一下你前者是轮询,后者是回调。
那你说,我直接在隔壁寝室等到同学回来可以吗?
可以啊,只不过这样原本你可以省下时间做其他事,现在必须浪费在等待上了。把原来的非阻塞的异步调用变成了阻塞的同步调用。
现在假设有两个函数 fun1和fun2,fun2等待fun1的执行结果,
fun1();
fun2();
再假设如果fun1是一个很耗时的任务,可以考虑改写fun1,吧fun2写成fun1的回调函数。
function fun1(callback){
setTimeout(function () {
//fun1代码任务
callback();//回调fun2
},1000);
}
fun1(fun2)
采用这种方式,我们把同步操作变成了异步操作,fun1不会堵塞程序运行,相当于先执行程序的主要逻辑,将耗时的操作推迟执行。
注意 区分 回调函数和异步
回调并不一定就是异步。他们自己并没有直接关系。
简单区分 同步回调 和 异步回调
同步回调 :
function A(callback){
console.log("I am A");
callback(); //调用该函数
}
function B(){
console.log("I am B");
}
A(B);
异步回调:因为js是单线程的,但是有很多情况的执行步骤(ajax请求远程数据,IO等)是非常耗时的,如果一直单线程的堵塞下去会导致程序的等待时间过长页面失去响应,影响用户体验了。
如何去解决这个问题呢,我们可以这么想。耗时的我们都扔给异步去做,做好了再通知下我们做完了,我们拿到数据继续往下走。
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true); //第三个参数决定是否采用异步的方式
xhr.send(data);
xhr.onreadystatechange = function(){
if(xhr.readystate === 4 && xhr.status === 200){
///do something
}
}
上面是一个代码,浏览器在发起一个ajax
请求,会单开一个线程去发起http请求,这样的话就能把这个耗时的过程单独去自己跑了,在这个线程的请求过程中,readystate
的值会有个变化的过程,每一次变化就触发一次 onreadystatechange
函数,进行判断是否正确拿到返回结果
采用事件驱动模式。
任务的执行不取决代码的顺序,而取决于某一时间是否发生。
监听函数有:on,bind,listen,addEventListener,observe
还是以fun1和fun2为例。首先,为fun1绑定一个事件(采用jquery写法)。
fun1.on('done',fun2);
上面代码意思是,当fun1发生done事件,就执行fun2。
function fun1(){
settimeout(function(){
//fun1的任务代码
fun1.trigger('done');
},1000);
}
fun1.trigger('done')表示,执行完成后,立即触发done事件,从而开始执行fun2.
这种方法的优点:比较容易理解,可以绑定多个事件,每一个事件可以指定多个回调函数,而且可以去耦合,有利于实现模块化。
这种方法的缺点:整个程序都要变成事件驱动型,运行流程会变得不清晰。
我们假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信号,其他任务可以向信号中心"订阅"(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做"发布/订阅模式"(publish-subscribe pattern),又称"观察者模式"(observer pattern)。
这个模式有多种实现,下面采用的是Ben Alman的Tiny Pub/Sub,这是jQuery的一个插件。
首先,fun2向"信号中心"jQuery订阅"done"信号。
jQuery.subscribe("done", fun2);
然后,fun1进行如下改写:
function fun1(){
setTimeout(function () {
// fun1的任务代码
jQuery.publish("done");
}, 1000);
}
jQuery.publish("done")的意思是,fun1执行完成后,向"信号中心"jQuery发布"done"信号,从而引发fun2的执行。
此外,fun2完成执行后,也可以取消订阅(unsubscribe)
jQuery.unsubscribe("done", fun2);
这种方法的性质与"事件监听"类似,但是明显优于后者。因为我们可以通过查看"消息中心",了解存在多少信号、每个信号有多少订阅者,从而监控程序的运行。
(1)promise对象是commonJS工作组提出的一种规范,一种模式,目的是为了异步编程提供统一接口。
(2)promise是一种模式,promise可以帮忙管理异步方式返回的代码。他讲代码进行封装并添加一个类似于事件处理的管理层。我们可以使用promise来注册代码,这些代码会在在promise成功或者失败后运行。
(3)promise完成之后,对应的代码也会执行。我们可以注册任意数量的函数再成功或者失败后运行,也可以在任何时候注册事件处理程序。
(4)promise有两种状态:1、等待(pending);2、完成(settled)。
promise会一直处于等待状态,直到它所包装的异步调用返回/超时/结束。
(5)这时候promise状态变成完成。完成状态分成两类:1、解决(resolved);2、拒绝(rejected)。
(6)promise解决(resolved):意味着顺利结束。promise拒绝(rejected)意味着没有顺利结束。
//promise
var p=new Promise(function(resolved))
//在这里进行处理。也许可以使用ajax
setTimeout(function(){
var result=10*5;
if(result===50){
resolve(50);
}else{
reject(new Error('Bad Math'));
}
},1000);
});
p.then(function(result){
console.log('Resolve with a values of %d',result);
});
p.catch(function(){
console.error('Something went wrong');
});
(1)代码的 关键在于setTimeout()的调用。
(2)重要的是,他调用了函数resolve()和reject()。resolve()函数告诉promise用户promise已解决;reject()函数告诉promise用户promise未能顺利完成。
(3)另外还有一些使用了promise代码。注意then和catch用法,可以将他们想象成onsucess和onfailure事件的处理程序。
(4)巧妙地方是,我们将promise处理与状态分离。也就是说,我们可以调用p.then(或者p.catch)多少次都可以,不管promise是什么状态。
(5)promise是ECMAscript 6管理异步代码的标准方式,javascript库使用promise管理ajax,动画,和其他典型的异步交互。
简单的说,它的思想是:每一个异步任务返回一个promise对象,该对象有一个then方法,允许指定回调函数。比如,fun1的回调函数fun2,可以写成:
fun1.then(fun2);
fun1要进行如下改写(使用jquery的实现):
function fun1(){
var dfd=$.deferred();
settimeout(function(){
//fun1的任务代码
dfd.resolve();
},500);
return dfd.promise;
}
这样写的优点:回调函数写成了链式写法,程序的流程可以看得很清楚,而且有一整套的配套方法,可以实现很多强大的功能。
比如,指定多个回调函数
fun1().then(fun2).then(fun3);
再比如,指定发生的错误时的回调函数:
fun1().then(fun2).fail(fun3);
而且,它有一个前面三种方法都没有的好处:如果一个任务已经完成,再添加回调函数,该回调函数会立即执行。
所以你不用担心错过某一个事件或者信号。
这种方法的缺点:编写和理解都相对比较难。
参考链接:https://www.cnblogs.com/leungUwah/p/7932912.html
参考链接:https://www.jianshu.com/p/1e75bd387aa0
同行莫回头,前端路上我们一起走。