JavaScript是一门编程语言,既然是编程语言那么就会有执行时的逻辑先后顺序,那么对于JavaScript来说这额顺序是怎样的呢?
首先我们我们需要明确一点,JavaScript是单线程语言。所谓单线程的意思就是只能按照一条逻辑线来运行,譬如你上班排队上公交车,先排先上,一个接一个。
首先我们需要清楚几个名词:
1.任务队列(task queue)
script中的每一个语句都是一个任务,譬如打印语句或者变量声明语句都是一个任务。
var monkey='熏悟空';
var pig='猪八盖';
如上面的代码就可以理解为两个任务。
而任务队列就是一个有规则的任务的集合,其规则就是先进先出。
2.同步任务与异步任务
所谓的同步任务就是当主线程运行到这里就能马上执行的任务,譬如:打印语句。
而异步任务则是需要等待一定时间后才能执行的语句,在这个过程中先运行他后面的语句,打个比方是猪扒盖去叫熏悟空打妖怪,熏悟空说“呆子,你先去,我去天上偷个蟠桃,看看七仙女后就来”。异步任务中的典型例子就是setTimeout延时任务。
console.log('熏悟空');
setTimeout(function(){
console.log('猪八盖')
},1000);
console.log('撒无尽');
当我主线程运行到异步任务setTimeout时就先注册一个回调函数,该函数是setTimeout中的内容,然后就去执行srtTimeout后面的打印语句。等到1000毫秒之后再去运行之前存储的回调函数。
具体两个相结合的运行顺序就是:
- 主线程自上而下执行代码。
- 同步任务直接执行,异步任务进入到事件注册表中并注册对应的回调函数。
- 等到异步任务的延时时间到了之后,事件注册表会将对应的回调函数添加到事件队列中。
- 主线程任务执行完毕之后,再去读取事件队列中的任务,将其进入到主线程中执行。
- 以上过程,不断循环往复。
以上不断循环的过程就是事件循环。
3.微任务与宏任务
javaScript不仅分为同步任务与异步任务,从另一个角度又分为了微任务与宏任务。
首先我们只需要记住几个特殊例子,也就是仅有的几个微任务:
1.Process.nextTick:node环境下的一个方法
2.Promise.then(.catch,.finally)注意这里是Promise的几个状态捕捉函数,而不是Promise本身
3.MutationObserver:元素节点的事件监听
除此以外的任务全都是宏任务,在同一个任务队列中先运行所有的同步任务,再执行异步任务,异步任务里面又包含了微任务与宏任务,先执行微任务,再执行宏任务。
小练:
console.log('俺是熏悟空');
setTimeout(function() {
console.log('俺是猪扒盖');
}, 0);
new Promise(function(resolve,reject){
console.log('俺是撒无尽');
resolve()
}).then(function() {
console.log('俺是白聋马');
})
console.log('俺是糖酸脏');
请问打印顺序是啥?
俺是熏悟空-->俺是撒无尽-->俺是糖酸脏-->俺是白聋马-->俺是猪扒盖。
逻辑理解:
1.主线程先执行‘俺是熏悟空’的同步打印任务
2.遇到setTimeout的异步任务,将其丢入事件注册表并注册对应回调函数,先不执行‘俺是猪扒盖’的打印任务
3.遇见Promise的函数执行‘俺是撒无尽’的同步打印任务。
4.遇见Promise.then异步任务,将其丢入事件注册表并注册对应回调函数,注意此时事件注册表已经有了一个setTimeout的回调,此时Promise.then的回调该放在setTimeout回调的前面还是后面呢,答案是前面,因为Promise.then属于微任务,优先级高于属于宏任务的setTimeout,先不执行‘俺是白聋马’的打印任务
5.执行‘俺是糖酸脏’的同步打印任务。
6.任务队列执行完毕,去事件注册表将延时时间(异步时间)已经到了的异步函数对应的回调函数添加到新的任务队列,再按照逻辑进行执行,先执行微任务Promise.then的回调函数,再执行setTimeout的回调函数
7.任务队列与事件注册表均无任务与事件,执行结束
以上需要注意的是
1.setTimeout的延时时间设置的是0,但是不会因为设置的0ms就变成了同步任务,不论设置多少都是异步任务,都是宏任务,因此执行顺序在同步任务之后,也在微任务之后。
2.为什么‘俺是撒无尽’的打印任务是同步打印呢,因此Promise的那几个状态捕捉函数才是异步任务,才是微任务,Promise本身函数里面的执行部分依旧是同步任务。