
JS为什么是单线程的
众所周知,JavaScript语言的一大特点就是单线程,也就是说js在运行时一次只做一件事。
为什么JavaScript不是多线程呢?我们都知道js的主要用途是与用户互动,以及操作DOM,如果两个线程同时操作一个DOM,一个增一个删同时进行,那浏览器应该听谁的呢,为了避免这些复杂性,从诞生之时js就是单线程的。
同样的问题,如果一个http请求数据过大或超时,就必须等他执行完再执行下一个任务了吗?这样页面岂不是卡死?导致执行效率低下。所以,浏览器为这些耗时任务开辟了另外的线程,主要包括http请求线程,浏览器定时触发器,浏览器事件触发线程,这些任务是异步的,并不在主线程上,但他们的执行顺序受主线程控制。
堆、栈和队列
这里先解释下堆(Heap)、栈(Stack)、队列(Queue)的概念也许会帮助大家更好的理解
1、 堆(Heap):存放对象。对象被分配在一个堆中,一个用以表示一个内存中大的未被组织的区域。
2、 栈(Stack):执行上下文的栈,先进后出, 存放一些var变量、function、dom操作、setTimeout等
function a(){
let a = 1;
b();
function b(){
let b = 2;
c();
function c(){
console.log(b);
}
}
}
a();
//函数a()、b()、c()依次放入栈中,但执行顺序是c()先执行

3、 队列(Queue):先进先出。js运行时异步代码没有进入主线程,进入到任务队列中,比如:ajax、setTimeout等。当执行栈为空时,会从队列中取出一个消息进行处理。当栈再次为空,会继续从队列中取,直至队列为空,消息处理完毕。这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。
同步与异步
什么是同步:线性执行。在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务,比如:
console.log(1);
console.log(2);
什么是异步:不进入主线程而进入"任务队列"的任务,只有等主线程任务执行完毕,"任务队列"开始通知主线程,请求执行任务,该任务才会进入主线程执行。常见有以下几种:
-
setTimeout、setInterval、setImmediate
-
Promise.then
-
process.nextTick 等
同步和异步任务分别进入不同的执行"场所":
1)所有同步任务都在主线程上执行,形成一个执行栈。
2)主线程之外,还存在一个"任务队列"。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
3)一旦"执行栈"中的所有同步任务执行完毕,就会读取"任务队列"可执行任务并执行。
4)主线程不断重复上面的第三步。
详细可见下图:

下边举个小栗子,看下执行结果,理解下代码的执行顺序:
console.log(1);
setTimeout(()=>{
console.log('setTimeout');
})
let promise = new Promise((resolve,reject)=>{
console.log('promise');
resolve(100);
}).then((data)=>{
console.log(data);
})
console.log(4);
//执行结果:1 promise 4 100 setTimeout
Promise.then和setTimeout不都是异步?不应该是谁先进入队列,谁先执行吗?为什么setTimeout最后输出?我们继续往下看 ⬇
微任务和宏任务
微任务和宏任务对任务进行更精细的划分:
-
微任务(micro-task):整段script、Promise.then、process.nextTick、MutationObserve、MessageChannel
-
宏任务(macro-task):setTimeout、setInterval、setImmediate
执行机制:
1)先执行主线程的同步代码
2)主线程代码执行完毕后先到“微任务”队列读取可执行代码并执行
3)上述执行完后,到“宏任务”队列中读取可执行代码并执行
4)依次重复2)3)。
了解完执行机制后,对上个例子做详细分析:
console.log(1); //同步代码
setTimeout(()=>{
console.log('setTimeout');
})
let promise = new Promise((resolve,reject)=>{
console.log('promise');
resolve(100);
}).then((data)=>{
console.log(data);
})
console.log(4); //同步代码
//先执行完同步代码,遇到Promise也是立即执行
//setTimeout放入对应的宏任务队列
//Promise.then放入对应的微任务队列
//同步代码执行完,会先执行微任务Promise.then,输出100,再执行宏任务setTimeout,输出setTimeout
//执行完毕