你可能知道,Javascript
语言的执行环境是"单线程"(single thread)。
所谓"单线程",就是指一次只能完成一件任务。若是有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推。
这种模式的好处是实现起来比较简单,执行环境相对单纯;坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),每每就是由于某一段Javascript
代码长时间运行(好比死循环),致使整个页面卡在这个地方,其余任务没法执行。
为了解决这个问题,Javascript
语言将任务的执行模式分红两种:同步(Synchronous)和异步(Asynchronous).
"异步模式"很是重要。在浏览器端,耗时很长的操做都应该异步执行,避免浏览器失去响应,最好的例子就是Ajax
操做。在服务器端,"异步模式"甚至是惟一的模式,由于执行环境是单线程的,若是容许同步执行全部http
请求,服务器性能会急剧降低,很快就会失去响应。javascript
setTimeout
函数的弊端延时处理固然少不了 setTimeout
这个神器,不少人对 setTimeout
函数的理解就是:延时为 n 的话,函数会在 n 毫秒以后执行。事实上并不是如此,这里存在三个问题:
一个是 setTimeout
函数的及时性问题, setTimeout
是存在必定时间间隔的,并非设定 n 毫秒执行,他就是 n 毫秒执行,可能会有一点时间的延迟,setInterval
和 setTimeout
函数运转的最短周期是 5ms 左右,这个数值在 HTML规范 中也是有提到的:css
Let timeout be the second method argument, or zero if the argument was omitted.
若是 timeout 参数没有写,默认为 0html
If nesting level is greater than 5, and timeout is less than 4, then increase timeout to 若是嵌套的层次大于 5 ,而且 timeout 设置的数值小于 4 则直接取 4.java
其次是while
循环会阻塞setTimeout
的执行jquery
看这段代码:
var t = true; window.setTimeout(function (){ t = false; },1000); while (t){} alert('end');
结果是死循环致使setTimeout
不执行,也致使alert
不执行
js是单线程,因此会先执行while(t){}
再alert
,但这个循环体是死循环,因此永远不会执行alert
。
至于说为何不执行setTimeout
,是由于js的工做机制是:当线程中没有执行任何同步代码的前提下才会执行异步代码,setTimeout
是异步代码,因此setTimeout
只能等js空闲才会执行,但死循环是永远不会空闲的,因此setTimeout
也永远不会执行。git
第三是,try..catch
捕捉不到他的错误github
这是异步编程最基本的方法。
假定有两个函数f1和f2,后者等待前者的执行结果。面试
function f1(callback){ setTimeout(function () { // f1的任务代码 callback(); }, 1000); } f1(f2);
采用这种方式,咱们把同步操做变成了异步操做,f1不会堵塞程序运行,至关于先执行程序的主要逻辑,将耗时的操做推迟执行。
回调函数的优势是简单、容易理解和部署,缺点是不利于代码的阅读和维护,各个部分之间高度耦合(Coupling),流程会很混乱,并且每一个任务只能指定一个回调函数。ajax
另外一种思路是采用事件驱动模式。任务的执行不取决于代码的顺序,而取决于某个事件是否发生数据库
f1.on('done', f2); function f1(){ setTimeout(function () { // f1的任务代码 f1.trigger('done'); }, 1000); }
JS 和 浏览器提供的原生方法基本都是基于事件触发机制的,耦合度很低,不过事件不能获得流程控制
Promises
对象Promises
对象是CommonJS
工做组提出的一种规范,目的是为异步编程提供统一接口。
Promises
能够简单理解为一个事务,这个事务存在三种状态:
已经完成了 resolved
由于某种缘由被中断了 rejected
还在等待上一个事务结束 pending
简单说,它的思想是,每个异步任务返回一个Promises
对象,该对象有一个then
方法,容许指定回调函数,这样写的优势在于,回调函数变成了链式写法,程序的流程能够看得很清楚
Promises
就是一个事务的管理器。他的做用就是将各类内嵌回调的事务用流水形式表达,其目的是为了简化编程,让代码逻辑更加清晰。
Promises
能够分为:
无错误传递的 Promises
,也就是事务不会由于任何缘由中断,事务队列中的事项都会被依次处理,此过程当中 Promises
只有 pending
和 resolved
两种状态,没有 rejected
状态。
包含错误的 Promises
,每一个事务的处理都必须使用容错机制来获取结果,一旦出错,就会将错误信息传递给下一个事务,若是错误信息会影响下一个事务,则下一个事务也会 rejected
,若是不会,下一个事务能够正常执行,依次类推。
此处留坑讲generator实现异步编程
原本想本身总结下generator与异步的,看了下阮一峰老师的博客算是了解个大概,理解也是只知其一;不知其二,有兴趣的话能够在底下的参考资料里找到去看看
jquery
的Deferred
对象简单说,Deferred
对象就是jquery
的回调函数解决方案。在英语中,defer的意思是"延迟",因此Deferred
对象的含义就是"延迟"到将来某个点再执行。
首先,回顾一下jquery
的ajax操做的传统写法:
$.ajax({ url: "test.html", success: function(){ alert("哈哈,成功了!"); }, error:function(){ alert("出错啦!"); } });
有了<ode>Deferred对象之后,写法是这样的:
$.ajax("test.html") .done(function(){ alert("哈哈,成功了!"); }) .fail(function(){ alert("出错啦!"); });
能够看到,done()
至关于success
方法,fail()
至关于error
方法。采用链式写法之后,代码的可读性大大提升。
了解jQuery.Deferred
对象能够看下面这个表格。
when.js
AngularJS
内置的Kris Kowal的Q框架,和cujoJS的when.js
,二者都是Promises/A
规范的实现when.js
实例
var getData = function() { var deferred = when.defer(); $.getJSON(api, function(data){ deferred.resolve(data[0]); }); return deferred.promise; } var getImg = function(src) { var deferred = when.defer(); var img = new Image(); img.onload = function() { deferred.resolve(img); }; img.src = src; return deferred.promise; } var showImg = function(img) { $(img).appendTo($('#container')); } getData() .then(getImg) .then(showImg);
看最后三行代码,是否是一目了然,很是的语义化
var deferred = when.defer();
定义了一个deferred
对象。
deferred.resolve(data);
在异步获取数据完成时,把数据做为参数,调用deferred
对象的resolve
方法。
return deferred.promise;
返回了deferred
对象的Promises
属性。
step.js
github地址step.js
是控制流程工具(大小仅 150 行代码),解决回调嵌套层次过多等问题。适用于读文件、查询数据库等回调函数相互依赖,或者分别获取内容最后组合数据返回等应用情景。异步执行简单地能够分为“串行执行”和“并行”执行
使用示例:
Step( function readSelf() { fs.readFile(__filename, this); }, function capitalize(err, text) { if (err) throw err; return text.toUpperCase(); }, function showIt(err, newText) { if (err) throw err; console.log(newText); } );
Step
的一个约定,回调函数的第一个参数老是 err,第二个才是值(沿用 Node
回调的风格)。若是上一个步骤发生异常,那么异常对象将被送入到下一个步骤中。
Javascript
既是单线程又是异步的,请问这两者是否冲突,以及有什么区别?Answer1:Javascript
自己是单线程的,并无异步的特性。
因为 Javascript
的运用场景是浏览器,浏览器自己是典型的 GUI 工做线程,GUI 工做线程在绝大多数系统中都实现为事件处理,避免阻塞交互,所以产生了 Javascript
异步基因。此后种种都源于此。
Answer2: JS的单线程是指一个浏览器进程中只有一个JS的执行线程,同一时刻内只会有一段代码在执行(你可使用IE的标签式浏览试试看效果,这时打开的多个页面使用的都是同一个JS执行线程,若是其中一个页面在执行一个运算量较大的function时,其余窗口的JS就会中止工做)。
而异步机制是浏览器的两个或以上常驻线程共同完成的,例如异步请求是由两个常驻线程:JS执行线程和事件触发线程共同完成的,JS的执行线程发起异步请求(这时浏览器会开一条新的HTTP请求线程来执行请求,这时JS的任务已完成,继续执行线程队列中剩下的其余任务),而后在将来的某一时刻事件触发线程监视到以前的发起的HTTP请求已完成,它就会把完成事件插入到JS执行队列的尾部等待JS处理。又例如定时触发(setTimeout
和setinterval
)是由浏览器的定时器线程执行的定时计数,而后在定时时间把定时处理函数的执行请求插入到JS执行队列的尾端(因此用这两个函数的时候,实际的执行时间是大于或等于指定时间的,不保证能准肯定时的)。
因此,所谓的JS的单线程和异步更多的应该是属于浏览器的行为,他们之间没有冲突,更不是同一种事物,没有什么区别不区别的。
setTimeout(fn,0)
当即执行的问题首先,不会当即执行,缘由:setTimeout(fn,0)
的做用很简单,就是为了把fn放到运行队列的最后去执行。也就是说,不管setTimeout(fn,0)
写在哪,均可以保证在队列的最后执行。js解析器会把setTimeout(fn,0)
里的fn压到队列的最后,由于它是异步操做。有个延时,具体是16ms仍是4ms取决于浏览器
当即执行仍是有可能的,只要在你调用setTimeout
的时候,知足下面两个条件:
恰好执行到了当前这一轮事件循环的底部。
恰好此时事件队列为空。
那么setTimeout
的回调函数就能够当即执行。固然“当即执行”的意思是在任何其余代码前执行。
知乎:setTimeout的异步以及js是单线程的面试题?
阮老师的博客
小胡子哥的博客
知乎:JavaScript 既是单线程又是异步的,请问这两者是否冲突,以及有什么区别?
细嗅Promise
whenjs文档
jQuery的deferred对象详解
jQuery 中的 Deferred 和 Promises (2)
Step.js 使用教程(附源码解析)
Generator 函数的含义与用法