前言
众所周知,JavaScript是一门单线程语言。单线程语言的代码是同步执行的,这就意味着会面临阻塞的问题。为了实现异步代码,JavaScript采用了一种特殊的执行机制以模拟“多线程”。并且在ES6中还添加了一个专门用于异步的新特性:Promise(期约)。
在介绍Promise之前,有必要先介绍一下JavaScript的运行机制:事件循环。
1、同步任务与异步任务
JS的执行机制指的就是事件循环(Event Loop),每一个JavaScript的"线程环境"都会有一个独立的Event Loop。在介绍事件循环之前,先看看下面这个例子:
setTimeout(console.log,0,1);
console.log(2);
// 2
// 1
由输出可知,setTimeout里面的函数是后执行的,或者说是经过一定时间后执行的。这类代码就是异步代码也称为异步任务。在JS中可以将任务分为同步任务和异步任务。
按照这种分类方法,JS的执行机制可以理解为:
- 先判断任务是同步任务还是异步任务,同步任务就推入主线程(执行栈)直接进行执行,异步任务就在Event Table进行注册;
- 满足异步任务的执行条件以后,就将异步任务推入事件队列(Event Queue)等待执行;
- 主线程空闲时,每隔一段时间就会检查事件队列中是否有异步任务等待执行,有就将任务推入主线程依次执行。 以上三步就是事件循环。
2、宏任务与微任务
同步任务和异步任务的分类方式其实并不严谨,JS的执行机制按照的其实是宏任务与微任务的分类方式。再来看看下面这个例子:
setTimeout(console.log,0,1)
//Promise是后面将要介绍的ES6特性,不了解的话,可以简单的先将then()里面的代码理解为异步任务。
Promise.resovle().then(()=>{
console.log(2);
})
console.log(3);
// 3
// 2
// 1
上述代码的执行情况,与我们之前所说的同步、异步任务执行过程,有所不同。按照之前所说的,setTimeout和Promise.then里面的代码都是异步任务,应该是依次推入Event Loop等待执行,输出应该是 312,但是得到却是 321。
原因就是,JS代码的分类方式是更准确的宏任务与微任务:
- macro-task(宏任务):包括整体代码
script,setTimeout,setInterval,UI交互事件等。 - micro-task(微任务):
Promise.then,process.nextTick等。 不同类型的任务会进入不同的Event queue。实际的执行过程是:先执行一次宏任务,宏任务结束后检查微任务队列(Microtask Queue),依次执行微任务,再检查事件队列,推入执行栈执行下一次宏任务。
执行下一次宏任务前,浏览器为了能够使得JS内部(macro)task与DOM任务能够有序的执行,会在一个(macro)task执行结束后,在下一个(macro)task 执行开始前,会对页面进行重新渲染。
setTimeout(console.log,0,1);
Promise.resolve().then(()=>console.log(2));
setTimeout(()=>{
console.log(3);
Promise.resolve().then(()=>console.log(4));
},0);
console.log(5);
setTimeout(console.log,0,6);
// 5
// 2
// 1
// 3
// 4
// 6
执行顺序:
- 执行整体代码
script, 期间产生了一个Promise.then微任务,注册了三个setTimeout异步回调,输出 5; - 执行栈空闲,执行微任务队列(直至为空),输出 2;
- 检查事件队列,因为三个
setTimeout回调都为0秒后触发,此时三个setTimeout回调都被推入了事件队列当中,将首个回调推入主线程(执行栈); - 执行回调,没有产生微任务,输出 1;
- 检查事件队列,还剩两个
setTimeout回调,将首个回调推入主线程(执行栈); - 执行回调,期间产生了一个
Promise.then微任务,输出 3; - 执行栈空闲,执行微任务队列(直至为空),输出 4;
- 检查事件队列,还剩一个
setTimeout回调,将首个回调推入主线程(执行栈); - 执行回调,没有产生微任务,输出 6; 注:从事件队列取出回调也属于宏任务,回调进入调用栈以后,主线程就不再空闲,进入执行状态,回调完成就是一次宏任务完成。
总结
本文作为介绍Promise的预热,简单介绍了JavaScript的运行机制:事件循环。
简而言之,事件循环的过程就是三步:宏任务执行结束->微任务队列执行结束->从消息队列获取下一个宏任务。下一节就会介绍Promise的意义和使用方法。