JS执行机制
一、运行机制
- JS分为同步任务和异步任务。
- 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
- 主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
- 一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
- 主线程不断重复上面的第三步。
二、宏任务、微任务
- macro-task(宏任务):包括执行整体的js代码,事件回调,XHR回调,定时器(setTimeout/setInterval/setImmediate),IO操作,UI render。
- micro-task(微任务):包括promise回调,MutationObserver,process.nextTick,Object.observe。
三、事件循环
- 检查macrotask队列是否为空,非空则到2,为空则到3
- 执行macrotask中的一个任务
- 继续检查microtask队列是否为空,若有则到4,否则到5
- 取出microtask中的任务执行,执行完成返回到步骤3
- 执行视图更新
四、练习
console.log('start');
setTimeout(()=>{
console.log('settimemout')
},0)
new Promise((resolve)=>{
console.log('promise')
resolve()
new Promise((resolve)=>{
setTimeout(resolve,0)
}).then(()=>{
console.log('promise1')
})
}).then(()=>{
console.log('promise2')
}).then(()=>{
console.log('promise3')
})
console.log('end')
// start promise end promise2 promise3 settimeout promise1
首先,全局代码压入调用栈执行,打印start;
接下来setTimeout放入macrotask队列,promise压入调用栈执行打印,外层promise.then回调放入microtask队列,setTimeout(resolve,0)放入macrotask队列,最后end被压入调用栈执行打印;
至此,调用栈中的代码被执行完成,回顾macrotask的定义,我们知道全局代码属于macrotask,macrotask执行完,那接下来就是执行microtask队列的任务了,执行外层promise.then回调打印promise2;
promise回调函数默认返回undefined,promise状态变为fullfill触发接下来的then回调,继续压入microtask队列,event loop会把当前的microtask队列一直执行完,此时执行外层第二个promise.then回调打印出promise3;
这时microtask队列已经为空,从上面的流程图可以知道,接下来主线程会去做一些UI渲染工作(不一定会做),然后开始下一轮event loop,执行setTimeout的回调,打印出setTimeout,执行setTimeout(resolve,0)回调,将内层promise回调放入microtask队列;
至此,macrotask队列已经为空,接下来是microtask队列的任务,将执行内层promise回调,打印promise1
这个过程会不断重复,也就是所谓的事件循环。
button.addEventListener('click', function( ev){
Promise.resolve().then(()=>{console.log('microTask 1')});
console.log('你点了一下');
}, true);
button.addEventListener('click', function(ev){
Promise.resolve().then(()=>{console.log('microTask 2')})
console.log('你又点了一下');
}, true);
- 对于以上代码,用户点击button按钮与使用JS触发button按钮的执行顺序并不相同。
当用户点击button按钮时,其执行结果为:
// 你点了一下
// microTask 1
// 你又点了一下
// microTask 2
当用户点击按钮时,首先将第一个事件监听压入调用栈,promise.then回调放入microtask队列。然后主栈执行,打印“你点了一下”,主栈清空,microtask队列执行,打印“microtask 1"。
第二个事件监听同理。
- 当使用Js触发button按钮点击时,其执行结果为:
button.click();
// 你点了一下
// 你又点了一下
// microTask 1
// microTask 2
当JS触发button点击时,主栈压入click()事件,click开始执行调度第一个事件监听,此时打印“你点了一下”,“microtask 1"放入微任务队列,接着click执行第二个事件监听,此时打印“你又点了一下”,“microtask 2"放入微任务队列。此时两个事件监听执行完毕,主栈清空,开始执行微任务队列,依次打印“microtask 1”、“microtask 2”。