Javascript是单线程运行的,单线程意味着代码在任务队列中会按照顺序一个接一个的执行。异步代表JavaScript代码在任务队列中的顺序并不完全等同于代码的书写顺序,比如事件绑定、Ajax、setTimeout()等任务的发生时间是“不可被预期”的。

引用知乎的一段话:

JavaScript引擎是单线程运行的,浏览器无论在什么时候都只且只有一个线程在运行JavaScript程序。
浏览器的内核是多线程的,它们在内核控制下相互配合以保持同步,一个浏览器至少实现三个常驻线程:JavaScript引擎线程,GUI渲染线程,浏览器事件触发线程。

一: JavaScript引擎是基于事件驱动单线程执行的,JavaScript引擎一直等待着任务队列中任务的到来,然后加以处理,浏览器无论什么时候都只有一个JavaScript线程在运行JavaScript程序。

二:GUI渲染线程负责渲染浏览器界面,当界面需要重绘(Repaint)或由于某种操作引发回流(Reflow)时,该线程就会执行。但需要注意,GUI渲染线程与JavaScript引擎是互斥的,当JavaScript引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到JavaScript引擎空闲时立即被执行。

三:事件触发线程,当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待JavaScript引擎的处理。这些事件可来自JavaScript引擎当前执行的代码块如setTimeout、也可来自浏览器内核的其他线程如鼠标点击、Ajax异步请求等,但由于JavaScript的单线程关系所有这些事件都得排队等待JavaScript引擎处理(当线程中没有执行任何同步代码的前提下才会执行异步代码)。

了解JavaScript单线程异步执行的机制以后,再来看一看setTimeout()与setInterval()在执行时候的具体情况。

看几个例子:


// 例子1

setTimeout(function(){
  console.log(1)
},0)
console.log(2)

//结果:
// 2
// 1

结果会先打印2,再打印1。这是因为运行的时候遇到setTimeout,此时会把setTimeout从队列里提出来放到运行队列后面去执行,所以无论setTimeout的执行顺序是在何处,都会保证在队列的最后执行。


// 例子2

console.log(1)
setTimeout(function () {console.log('a')}, 10);
setTimeout(function () {console.log('b')}, 0);
var sum = 0;
for (var i = 0; i < 1000000; i ++) {
    sum += i;
}
console.log(sum);
setTimeout(function () {console.log('c');}, 0);

//结果:
// 1
// 499999500000
// b
// a
// c

由于这个for循环是一个耗时的操作,而javascript是单线程运行,所以console.log('a')先被插入任务队列,等for循环结束后才把console.log('c')插入队列,所以c是最后输出。


// 例子3

console.log(1)
setTimeout(function () {console.log('a')}, 10);
setTimeout(function () {console.log('b')}, 0);
var sum = 0;
for (var i = 0; i < 10; i ++) {
    sum += i;
}
console.log(sum);
setTimeout(function () {console.log('c');}, 0);

//结果:
// 1
// 45
// b
// c
// a

由于for循环执行时间小于10ms,所以c先被插入到队列里


// 例子4

var start = new Date();
setTimeout(function(){
    var end = new Date();
    console.log("Time elapsed: ", end - start, "ms");
}, 500);

while (new Date - start <= 1000)
{

}
// 结果:
// Time elapsed:  1003 ms

执行后,会发现setTimeout函数是1s后才执行的,因为javascript是单线程运行的,也就是无法同一时候运行多段代码,while的任务耗时比setTimeout长,虽然setTimeout设置了500ms后执行,但是while还没有执行完,执行完while这个任务才会继续执行setTimeout。