javascript是一门单线程语言,即一次只能完成一个任务,若有多个任务要执行,则必须排队按照队列来执行(前一个任务完成,再执行下一个任务)。
这种模式执行简单,但随着日后的需求,事务,请求增多,这种单线程模式执行效率必定低下。只要有一个任务执行消耗了很长时间,在这个时间里后面的任务就无法执行。常见的浏览器无响应(假死),往往就是因为某一段Javascript代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。(弊端)
为了解决这个问题,javascript语言将任务执行模式分成同步和异步:
同步模式:就是上面所说的一种执行模式,后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的。
异步模式:就是每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。
“异步模式”非常重要。在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应,最好的例子就是Ajax操作。在服务器端,”异步模式”甚至是唯一的模式,因为执行环境是单线程的,如果允许同步执行所有http请求,服务器性能会急剧下降,很快就会失去响应。(异步模式的重要性)
下面就带来几种前端异步解决方案:
一.传统方案
1.回调函数(callback):
异步编程的基本方法。
首先需要声明,回调函数只是一种实现,并不是异步模式特有的实现。回调函数同样可以运用到同步(阻塞)的场景下以及其他一些场景。
回调函数的定义:
函数A作为参数(函数引用)传递到另一个函数B中,并且这个函数B执行函数A。我们就说函数A叫做回调函数。如果没有名称(函数表达式),就叫做匿名回调函数。
生活举例:约会结束后你送你女朋友回家,离别时,你肯定会说:“到家了给我发条信息,我很担心你。” 然后你女朋友回家以后还真给你发了条信息。其实这就是一个回调的过程。你留了个参数函数(要求女朋友给你发条信息)给你女朋友,然后你女朋友回家,回家的动作是主函数。她必须先回到家以后,主函数执行完了,再执行传进去的函数,然后你就收到一条信息了。
案例:
//定义主函数,回调函数作为参数functionA(callback) { callback(); console.log('我是主函数'); } //定义回调函数functionB(){ setTimeout("console.log('我是回调函数')", 3000);//模仿耗时操作 } //调用主函数,将函数B传进去A(B); //输出结果我是主函数我是回调函数
上面的代码中,我们先定义了主函数和回调函数,然后再去调用主函数,将回调函数传进去。
定义主函数的时候,我们让代码先去执行callback()回调函数,但输出结果却是后输出回调函数的内容。这就说明了主函数不用等待回调函数执行完,可以接着执行自己的代码。所以一般回调函数都用在耗时操作上面。比如ajax请求,比如处理文件等。
优点:简单,容易理解和 部署。
缺点:不利于代码的阅读,和维护,各部分之间高度耦合,流程会很混乱,而且每一个任务只能指定一个回调函数。
2.事件监听
采用事件驱动模式。
任务的执行不取决代码的顺序,而取决于某一个事件是否发生。
监听函数有:on,bind,listen,addEventListener,observe
以f1和f2为例。首先,为f1绑定一个事件(采用jquery写法)。
f1.on('done',f2);
上面代码意思是,当f1发生done事件,就执行f2。
然后对f1进行改写:
functionf1(){ settimeout(function(){ //f1的任务代码f1.trigger('done'); },1000); }
f1.trigger('done')表示,执行完成后,立即触发done事件,从而开始执行f2.
优点:比较容易理解,可以绑定多个事件,每一个事件可以指定多个回调函数,而且可以去耦合,有利于实现模块化。
缺点:整个程序都要变成事件驱动型,运行流程会变得不清晰。
事件鉴定方法:
(1).onclick方法:
element.onclick=function(){ //处理函数 }
优点:写法兼容到主流浏览器。
缺点:当同一个element元素绑定多个事件时,只有最后一个事件会被添加。
例如:
element.onclick=handler1; element.onclick=handler2; element.onclick=handler3;
上诉只有handler3会被添加执行,所以我们使用另外一种方法添加事件。(2)attachEvent和addEvenListener方法
(2).attachEvent和addEvenListener方法:
//IE:attachEvent(IE下的事件监听)elment.attachEvent("onclick",handler1); elment.attachEvent("onclick",handler2); elment.attachEvent("onclick",handler3);
上述三个方法执行顺序:3-2-1;
//标准addEventListener(标准下的监听)elment.addEvenListener("click",handler1,false); elment.addEvenListener("click",handler2,false); elment.addEvenListener("click",handler3,false);>
执行顺序:1-2-3;
PS:该方法的第三个参数是冒泡获取(useCapture),是一个布尔值:当为false时表示由里向外(事件冒泡),true表示由外向里(事件捕获)。
<div id="id1"> <div id="id2"></div> </div>
document.getElementById("id1").addEventListener("click",function(){ console.log('id1'); },false); document.getElementById("id2").addEventListener("click",function({ console.log('id2'); },false); //点击id=id2的div,先在console中输出,先输出id2,在输出id1document.getElementById("id1").addEventListener("click",function({ console.log('id1'); },false); document.getElementById("id2").addEventListener("click",function({ console.log('id2'); },true); //点击id=id2的div,先在console中输出,先输出id1,在输出id2
(3).DOM方法addEventListener()和removeListenner():
addEventListenner()和removeListenner()表示用来分配和删除事件的函数。这两种方法都需要三种参数,分别为:string(事件名称),要触发事件的函数function,指定事件的处理函数的时期或者阶段(boolean)。例子见(2)
(4).通用的时间添加方法:
on:function(elment,type,handler){ //添加事件returnelement.attachEvent?elment.attachEvent("on"+type,handler):elment.addEventListener(type,handler,false); }
事件冒泡和事件捕获的区别,可以参考:
二.工具方案
工具方案大致分为以下5个:
Promise
gengerator函数
async await
node.js中 nextTick setImmidate
第三方库 async.js
下面针对每一个做详细说明应用:
1.Promise(重点)
(1).Promise的含义和发展:
含义:Promise 对象用于一个异步操作的最终完成(或失败)及其结果值的表示。简单点说,它就是用于处理异步操作的,异步处理成功了就执行成功的操作,异步处理失败了就捕获错误或者停止后续操作。
发展:Promise 是异步编程的一种解决方案,比传统的解决方案–回调函数和事件--更合理和更强大。它由社区最早提出和实现,ES6将其写进了语言标准,统一了语法,原生提供了Promise
(2).它的一般形式:
newPromise( /* executor */function(resolve, reject) { if (/* success */) { // ...执行代码resolve(); } else { /* fail */// ...执行代码reject(); } } );
其中,Promise中的参数executor是一个执行器函数,它有两个参数resolve和reject。它内部通常有一些异步操作,如果异步操作成功,则可以调用resolve()来将该实例的状态置为fulfilled,即已完成的,如果一旦失败,可以调用reject()来将该实例的状态置为rejected,即失败的。
我们可以把Promise对象看成是一条工厂的流水线,对于流水线来说,从它的工作职能上看,它只有三种状态,一个是初始状态(刚开机的时候),一个是加工产品成功,一个是加工产品失败(出现了某些故障)。同样对于Promise对象来说,它也有三种状态:pending: 初始状态,也称为未定状态,就是初始化Promise时,调用executor执行器函数后的状态。 fulfilled:完成状态,意味着异步操作成功。
pending:初始状态,也称为未定状态,就是初始化Promise时,调用executor执行器函数后的状态。
fulfilled:完成状态,意味着异步操作成功。
rejected:失败状态,意味着异步操作失败。
它只有两种状态可以转化,即
操作成功:pending -> fulfilled
操作失败:pending -> rejected
注意:并且这个状态转化是单向的,不可逆转,已经确定的状态(fulfilled/rejected)无法转回初始状态(pending)。
(3).Promise对象的方法(api):
1):Promise.prototype.then(callback)
Promise对象含有then方法,then()调用后返回一个Promise对象,意味着实例化后的Promise对象可以进行链式调用,而且这个then()方法可以接收两个函数,一个是处理成功后的函数,一个是处理错误结果的函数。
如下:
varpromise1=newPromise(function(resolve, reject) { // 2秒后置为接收状态setTimeout(function() { resolve('success'); }, 2000); }); promise1.then(function(data) { console.log(data); // success}, function(err) { console.log(err); // 不执行}).then(function(data) { // 上一步的then()方法没有返回值console.log('链式调用:'+data); // 链式调用:undefined }).then(function(data) { // ....});
在这里我们主要关注promise1.then()方法调用后返回的Promise对象的状态,是pending还是fulfilled,或者是rejected?
返回的这个Promise对象的状态主要是根据promise1.then()方法返回的值,大致分为以下几种情况:
1.如果then()方法中返回了一个参数值,那么返回的Promise将会变成接收状态。
2.如果then()方法中抛出了一个异常,那么返回的Promise将会变成拒绝状态。
3. 如果then()方法调用resolve()方法,那么返回的Promise将会变成接收状态。
4. 如果then()方法调用reject()方法,那么返回的Promise将会变成拒绝状态。
5.如果then()方法返回了一个未知状态(pending)的Promise新实例,那么返回的新Promise就是未知 状态。
6.如果then()方法没有明确指定的resolve(data)/reject(data)/return data时,那么返回的新Promise就是接收状态,可以一层一层地往下传递。
2):Promise.prototype.catch(callback)
catch()方法和then()方法一样,都会返回一个新的Promise对象,它主要用于捕获异步操作时出现的异常。因此,我们通常省略then()方法的第二个参数,把错误处理控制权转交给其后面的catch()函数,如下:
varpromise3=newPromise(function(resolve, reject) { setTimeout(function() { reject('reject'); }, 2000); }); promise3.then(function(data) { console.log('这里是fulfilled状态'); // 这里不会触发// ...}).catch(function(err) { // 最后的catch()方法可以捕获在这一条Promise链上的异常console.log('出错:'+err); // 出错:reject});