前言
-
JS单线程的局限
由于JS"单线程"的特点,一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务。但是,只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),往往就是因为某一段Javascript代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。
-
解决办法
为了解决这个问题,JS添加了异步执行模式。"异步模式"每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。
一、事件监听
事件监听是采用事件驱动模式,任务的执行不再取决于代码的顺序,而是取决于某个事件是否发生。
请求1(function(请求结果1).on('done', 请求2(function(请求结果2));
当请求1发生done事件,就执行请求2,改写为:
请求1(function(请求结果1)){
setTimeout(function () {
请求1.trigger('done');
}, 1000);
}
请求.trigger('done')表示,执行完成后,立即触发done事件,从而开始执行请求。
-
优点
这种方法的优点是比较容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,而且可以"去耦合",有利于实现模块化。 -
缺点
缺点是整个程序都要变成事件驱动型,运行流程会变得很不清晰。
二、回调函数
需要根据前一个网络请求的结果,再去执行下一个网络请求,代码大概如下:
请求1(function(请求结果1){
setTimeout(function () {
请求2(function(请求结果2){
...
})
}, 1000);
}
这样,就将同步变成了异步操作,请求1不会阻塞程序运行,先执行程序的主要逻辑,将耗时的操作推迟执行。
-
优点
回调函数的优点是简单、容易理解和部署。 -
缺点
缺点是不利于代码的阅读和维护,各个部分之间高度耦合,流程会很混乱,而且每个任务只能指定一个回调函数。
随着回调函数的增加,很容易陷入回调地狱,为了解决回调地狱的问题,一般采用Promise作为解决方案。
三、发布/订阅
我们可以监听某一事件,当事件发生时,进行相应回调操作;另一方面,当某些操作完成后,通过发布事件触发回调。这样就可以将原本捆绑在一起的代码解耦。
jQuery.subscribe("done", f2);
改写:
请求1(function(请求结果1){
setTimeout(function () {
jQuery.publish("done");
}, 1000);
}
jQuery.publish("done")的意思是,请求1执行完成后,向"信号中心"jQuery发布"done"信号,从而引发请求2的执行。此外,请求2完成执行后,也可以取消订阅(unsubscribe)。
这种方法的性质与"事件监听"类似,但是明显优于后者。因为我们可以通过查看"消息中心",了解存在多少信号、每个信号有多少订阅者,从而监控程序的运行。
四、Promise
Promise 是异步编程的一种解决方案,比传统的异步解决方案【回调函数】和【事件】更合理、更强大。现已被 ES6 纳入进规范中。
Promise是一个方案,用来解决多层回调嵌套的解决方案。
需要根据前一个网络请求的结果,再去执行下一个网络请求,代码大概如下:
请求1(function(请求结果1){
请求2(function(请求结果2){
请求3(function(请求结果3){
请求4(function(请求结果4){
请求5(function(请求结果5){
请求6(function(请求结果3){
...
})
})
})
})
})
})
回调地狱带来的负面作用有以下几点:
1、代码臃肿;
2、可读性差;
3、耦合度过高,可维护性差;
4、代码复用性差;
5、容易滋生 bug;
6、只能在回调里处理异常;
为了能使用一种更加友好的代码组织方式,解决异步嵌套的问题:
let 请求结果1 = 请求1();
let 请求结果2 = 请求2(请求结果1);
let 请求结果3 = 请求3(请求结果2);
let 请求结果4 = 请求2(请求结果3);
let 请求结果5 = 请求3(请求结果4);
-
优点
这样写的优点在于,回调函数变成了链式写法,程序的流程可以看得很清楚,可以实现很多强大的功能。如果一个任务已经完成,再添加回调函数,该回调函数会立即执行。所以,你不用担心是否错过了某个事件或信号。 -
缺点
缺点就是编写和理解,都相对比较难。
扩展(Generator和async / await)
-
Generator
generator是es6中的一个新的语法。在function关键字后添加*即可将函数变为generator
const gen = function* () {
yield 1;
yield 2;
return 3;
}
let g = gen();
g.next(); // { value: 1, done: false }
g.next(); // { value: 2, done: false }
g.next(); // { value: 3, done: true }
g.next(); // { value: undefined, done: true }
-
async/await
es7中的async/await
在async函数中可以使用await语句,await后一般是一个Promise对象。
async function foo () {
console.log('开始');
let res = await post(data);
console.log(`post已完成,结果为:${res}`);
};
网友评论