前言
关于Javascript的执行顺序,众所周知是按照顺序自上而下执行。但是在我们面试过程中。总会遇到面试官问:这段代码是如何执行的,输出结果是怎样的,然后再讲下为什么。对于这种问题,难的不是输出什么,而是为什么,输出结果我们可以像做选择题一样输出,但是为什么才是问题的关键,但我们很少能够答道点上。
为什么会有同步异步
JavaScript是单线程执行的语言,在同一时间只能做一件事。这就导致后面的任务需要等到前面的任务执行完成才能执行。如果前面的任务执行很耗时,就会造成后面的任务一直等待。为了解决这个问题JavaScript中出现了同步任务和异步任务。
同步任务
在主线程上排队执行的任务。只有前一个任务执行完成,后一个任务才能执行,形成了一个执行栈。
异步任务
不进入主线程,而是进入任务队列。当主进程中的任务执行完毕,就会从任务队列中取出任务放进主线程中来执行。
事件循环
由于主线程一直不断的重复获得任务、执行任务,再获取再执行,这种机制就叫做事件循环(Event Loop)。
总结
- 所有同步任务都在主线程上执行,形成一个执行栈。
- 除了主线程外,还有个任务队列。
- 主线程完成所有任务(执行栈清空),就会读取任务队列,先执行微任务再执行宏任务。
- 以上三步会不停的重复。
先看一道面试题,输出结果???
为什么300比200先输出???这就涉及到宏任务和微任务。
宏任务
setTimeout
setInterval
Ajax
DOMs事件
微任务
Promise
async/await
在了解执行顺序之前我们先记住一点:微任务执行时机比宏任务要早。
在ES6中,Microtask称为jobs(微任务)、Macrotask称为task(宏任务),宏任务是由宿主(浏览器、node)发起的,而微任务是由JavaScript自身发起的。
| note | 宏任务 | 微任务 |
|---|---|---|
| 任务发起 | 宿主(node、浏览器) | JS引擎 |
| 事件 | 1.script 2.setTimeout/setInterval 3. UI rendering/UI事件 4. postMessage,MessageChannel 5. setImmediate,I/O(Node.js) | 1. Promise 2. MutaionObserver 3. Object.observe(已废弃;Proxy 对象替代) 4. process.nextTick(Node.js) |
| 运行顺序 | 后运行 | 先运行 |
| 会触发新一轮的Tick吗? | 会 | 不会 |
宏任务、微任务怎么执行的?
执行顺序:先执行同步代码,遇到异步宏任务则将异步宏任务放入宏任务队列。遇到异步微任务则将异步微任务放入微任务队列中。当所有同步代码执行完毕后,在将异步微任务从队列中调入主线程执行,微任务执行完毕后再将异步宏任务从队列中调入主线程执行,一直循环至所有任务执行完毕。
案例、练习
解析
1.遇到setTimout,异步宏任务,放入宏任务队列中
2.遇到new Promise,new Promise在实例化的过程中所执行的代码都是同步进行的,所以输出2
3.而Promise.then中注册的回调才是异步执行的,将其放入微任务队列中
4.遇到同步任务console.log(‘5’);输出5;主线程中同步任务执行完
5.从微任务队列中取出任务到主线程中,输出3、 4,微任务队列为空
6.从宏任务队列中取出任务到主线程中,输出1,宏任务队列为空
案例
1.遇到setTimeout,异步宏任务,将() => {console.log(4)}放入宏任务队列中;
2.遇到new Promise,new Promise在实例化的过程中所执行的代码都是同步进行的,所以输出1;
3.而Promise.then中注册的回调才是异步执行的,将其放入微任务队列中
4.遇到同步任务console.log(2),输出2;主线程中同步任务执行完
5.从微任务队列中取出任务到主线程中,输出3,此微任务中又有微任务,Promise.resolve().then(微任务a).then(微任务b),将其依次放入微任务队列中;
6.从微任务队列中取出任务a到主线程中,输出 before timeout;
7.从微任务队列中取出任务b到主线程中,任务b又注册了一个微任务c,放入微任务队列中;
8.从微任务队列中取出任务c到主线程中,输出 also before timeout;微任务队列为空
9.从宏任务队列中取出任务到主线程,此任务中注册了一个微任务d,将其放入微任务队列中,接下来遇到输出4,宏任务队列为空
10.从微任务队列中取出任务d到主线程 ,输出test,微任务队列为空