JavaScript的运行机制一直是绕不开的话题,众所周知,JavaScript是单线程 + 异步队列的模式运行的,如何看待这种执行机制呢?同步异步,任务队列,又是怎样分配好这之中的先与后?
为什么要单进程?
JavaScript是运行再浏览器中的,DOM对象是JavaScript一个很重要的组成部分,直接或间接操作DOM也是JavaScript做得最多的那些事,当然node服务端或者dena开发者的除外。
既然运行环境是在浏览器,操作DOM,假如说JavaScript是多线程的,如果线程A删除了某个DOM,线程B又要编辑这个DOM,这是两个矛盾的过程,请问浏览器要听谁的?
既然单线程已成了必然,但是单线程不会执行很慢吗?因为各种各样的阻塞。这就是JavaScript面临的第二个问题。而JavaScript也有了一个很独特但很cool的解决方式:
异步队列
JavaScript在执行的过程中:
会先判断执行的JS代码段(是的,JavaScript是一段一段执行的而非一行行执行)是同步还是异步,同步就进入主进程,异步就进入event table(你可以认为是大门签到处)
异步任务在event table中注册函数,当满足触发条件后,被推入event queue(队列)
同步任务进入主线程后一直执行,直到主线程空闲时(通常同步代码很快就执行完),才会去event queue中查看是否有可执行的异步任务,如果有就推入主进程中。
JavaScript会不断的循环这个过程,即event loop。
我们可以先来看一下这段代码:
console.log('同步1'); setTimeout(() => console.log('异步1'),0); console.log('同步2');
你会发现输出结果是:同步1 同步2 异步1,尽管异步1写在同步2的代码之前,而JavaScript实际上的执行是这样的:
看看下面稍微复杂点的题:
async function async1() { console.log('async1 start'); await async2(); console.log('async1 end'); } async function async2() { console.log('async2'); } console.log('script start'); setTimeout(function() { console.log('setTimeout'); }, 0) async1(); new Promise(function(resolve) { console.log('promise1'); resolve(); }).then(function() { console.log('promise2'); }); console.log('script end');
而最终的执行结果是:
其实明白微任务与宏任务之间在队列中执行的优先级,这个过程中同步的先执行同步任务:
console.log('script start');
接下来执行到:
async1();
async1()内部先直接执行
console.log('async1 start');
输出后,执行async2,async2内部执行这一条。
console.log('async2');
继续往下执行同步任务。
遇到 new promise,先输出
console.log('promise1');
遇到承诺resolve,需要后续的捕获。这部分扔进队列中。
同步继续进行到。
console.log('script end');
再回过头来执行异步队列中的任务。
此时的异步队列中有什么任务呢?
有宏任务setTimeout,微任务promise,跟await,setTimeout是宏任务放最后,依次输出:
console.log('promise2');
console.log('async1 end');
再最后输出宏任务setTimeout中的任务
console.log('setTimeout');
对于JavaScript的执行机制就暂时介绍到这啦。
热门评论