大家可能都知道,JS语言的执行环境是单线程的。
所谓单线程就是指一次只能完成一件任务,如果有多个任务,就必须排队,等前面一个任务执行完成,再执行后面一个任务,依次进行。
好处:实现起来比较简单,执行环境相对单纯。
缺点:只要存在一个任务耗时很久,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死)往往就是因为某一段JS代码长时间运行(比如死循环),导致整个页面卡在某个任务,其它任务无法执行。
那么为了解决这个问题。JS将任务的执行模式分为两种:同步(synchronous) 和 异步(asynchronous)。
同步上面已经介绍过,即后一个任务等前一个任务结束后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的。
异步模式则完全不同,每一个任务有一个多多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序不是一致的。
异步模式非常重要。在浏览器端,耗时很长的操作都应该是异步执行,避免浏览器失去响应,最好的列子就是ajax操作。
异步调用不会阻止代码的顺序执行,而是在将来的某一个时刻触发设置好的逻辑,所以我们:
1.并不知道逻辑什么时候会被调用;
2.只能定义当触发的时候逻辑是什么;
3.只能等待,同时可以去处理其他的逻辑;
setTimeout就是这样的一个异步调用。
var a = 1; function f1(a){ console.log("I am in f1"); setTimeout(function(){a+=2;console.log("f1:" + a);},1000)} f1(a); console.log("out:" + a);
得到的结果如下图所示:
I am in f1
out:1
f1:3
结果表明代码执行过程中确实是先执行了f1函数,在执行console.log输出后,setTimeout的回调函数在执行过程中被挂起,知道1s后才被调用,在等待执行setTImeout回调函数的时间里,JS选择执行后面的任务,即执行了console.log("out:" + a);
实际上,异步函数,如setTimeout和setInterval,是被压入了称之为Event Loop的队列。
Event Loop是一个回调函数队列。当异步函数执行时,回调函数会被压入这个队列。JavaScript引擎直到异步函数执行完成后,才会开始处理事件循环。这意味着JavaScript代码不是多线程的,即使表现的行为相似。事件循环是一个先进先出(FIFO)队列,这说明回调是按照它们被加入队列的顺序执行的。JavaScript被 node选做为开发语言,就是因为写这样的代码多么简单啊。