首先,我们要知道,JavaScript的本质是一门浏览器脚本语言,在执行的时候是一行一行的执行,只有前面的代码执行完了才会执行后面的代码。JS是单线程语言指的就是这个意思。
同步和异步其实在进行任务执行顺序时候都只有一条流水线,区别在于执行任务的顺序不同。
对于同步任务和异步任务,打个比喻:
有一堆学生在食堂排队打饭,然后进门的时候有些学生领了个异步的牌子,有些学生没有领,然后在窗口前排队打饭的时候,食堂大叔规定,有异步牌子的学生出来重新组成一个小队列,在窗口旁边等待,没有牌子的学生仍然在窗口前的主队里排队打饭。等主队里没有牌子的学生排队打完饭之后,食堂大叔示意小队列里有异步牌子的学生一个个的过来打饭。
抛开这个场景不谈,在js中,同步和异步的概念,有很多博主的解释就是“同步就是任务一个接一个的执行,前面的没有执行完成后面的就一直等待”,“异步就是觉得一个任务要执行会花很长时间,所以先放着,去执行其他的任务,等轮到这个任务的时候再执行。”
很多小伙伴看到这里就懵逼了,怎么着,系统还能识别哪个任务执行时间长哪个短?有这么智能好用的系统?要是系统自动识别执行时长,任务管理不会乱套?所以,我个人觉得这些博主的描述是有很大问题的。
一个任务是否是异步的是看程序员在编写代码的时候是否将这个任务设置为异步,而不是说时间长的任务就一定是异步的,只是通常来讲为了流畅性,编程者会将执行时间长的任务主观的设置为异步。
所以说只要编程者不人为的对任务的执行顺序进行干涉,那么任务就是同步的,会一条一条的执行。但是只要编程者人为的对某些任务的执行顺序进行干涉,就是进行异步操作。
那么这里就涉及到一个问题了,怎么将一个任务设置为异步?
JS中最基础的有两种方式——setTimeout函数和setInterval函数。
如下代码:
1 <script type="text/javascript"> 2 console.log('1'); 3 setTimeout(function(){ 4 console.log('2'); 5 },0) 6 setTimeout(function(){ 7 console.log('3'); 8 },0) 9 setTimeout(function(){ 10 console.log('4'); 11 },0) 12 console.log('5'); 13 </script>
上述代码输出顺序为:1 5 2 3 4
其中因为2 3 4的输出语句使用了setTimeout函数将其设定了等待时间(虽然设置的等待时间为0),所以它们3个被抽出来放在了另外的队列里,只有没被特别设置的任务按顺序执行完之后才会开始执行这个特殊队列的任务。所以会先把正常的1 5打印,然后才会开始打印2 3 4
这里我们会发现,1和5是按一般顺序打印,因为JS是逐行执行的。但是同为异步任务的2 3 4的打印任务也是按照2 3 4的顺序打印,是受加入队列的顺序影响。那么是不是说异步任务组成的队列里执行顺序和加入异步队列的顺序有关呢?并不是。只有异步任务的等待时间相同的时候,异步任务的执行顺序才会收到计入队列的顺序影响。否则的话主要还是与每个任务设置的等待时间有关系。
例如下面这种情况:
1 <script type="text/javascript"> 2 console.log('1'); 3 setTimeout(function(){ 4 console.log('2') 5 },300) 6 setTimeout(function(){ 7 console.log('2') 8 },200) 9 setTimeout(function(){ 10 console.log('2') 11 },100) 12 console.log('5'); 13 </script>
上述代码的输出顺序为1 5 4 3 2
因为异步任务设置的等待时间不同,在同步任务执行完后,等待时间较短的任务优先执行,等待时间长的后执行。
把这些都搞清楚之后我们又难免会思考,这样的异步操作都是对于只需要执行一次的任务进行排序,假如是要对多个需要按一定顺序循环执行的异步任务呢?这个时候就需要用到promise队列的链式调用,并且使用一个全局变量,在每完成一次循环后重置全局变量的值,并且在循环执行前检查该全局变量的值。
详细思路和是西安方法可以参考以下这篇博文。