上篇文章介绍了进程和线程。你知道渲染进程有一个主线程,主线程做了很多工作,处理DOM和计算样式。我们都知道JS是单线程的,任务只能一个一个执行,那么浏览器是如何让这么多类型的任务在主线程上有序执行的呢?执行了什么?这需要任务队列和事件循环。任务队列(消息队列)什么是任务队列?它是一种存储要执行的任务的数据结构。然后事件循环系统按照先进先出的原则依次执行队列中的任务。当有新任务产生时,IO线程会将任务添加到队列尾部。执行任务,渲染主线程会从队列头部取出,循环执行。如图,如果其他进程也有想要主线程执行的任务,同样是通过IO线程接收并加入任务到任务队列中,但是任务队列中的任务类型太多了,多个线程操作同一个任务队列,比如鼠标滚动、点击、移动、输入、定时器、WebSocket、文件读写、解析DOM、计算样式、计算布局、JS执行……这些任务都在执行主线程,而JS是单线程的。一个任务执行需要等待前面的任务执行完毕,所以需要解决单个任务占用主线程时间过长的问题对于任务来说,每次变化都需要调用相应的JavaScript接口,这无疑会拉长任务时间。如果将DOM更改做成一个异步任务,它可能会被添加到任务队列进程中。队列前面有很多任务,所以为了处理高优先级的任务,以及解决单个任务执行时间过长的问题,所以需要对任务进行划分,于是微任务和宏任务就来了.在讲微任务之前,我们需要了解一个同步和异步的概念。浏览器页面由任务队列和事件循环系统驱动,但是队列必须一个一个执行。如果一个任务(http请求)是一个耗时任务,浏览器不可能一直卡住,所以为了防止主线程阻塞,JavaScript分为同步任务和异步任务。同步任务:任务一个接一个执行。需要一段时间才能返回。这时候把这个任务放到一个专门处理异步任务的模块中,然后继续执行,不会因为这个任务而阻塞。也就是说,除了任务队列之外,还有一个特殊的处理需要延迟。执行的模块(延迟哈希表)常见的异步任务:timer、ajax、事件绑定、回调函数、asyncawait、promise好了,说说microtasks在JS中执行microtasks和macrotasks时,V8会为它们创建一个全局的执行上下文。在创建上下文的同时,V8也会在内部创建一个微任务队列。有微任务队列,自然也有宏任务队列。任务队列中的每个任务称为宏任务。在当前宏任务执行过程中,如果有新的微任务产生,会被加入到微任务队列中。微任务包括:promise回调、代理、MutationObserver(监控DOM),node中的process.nextTick等宏任务包括:渲染事件、请求、脚本、Node中的setTimeout、setInterval、setImmediate、I/O等,看栗子了解她。在你面前,大叔要省钱。存完钱,工作人员问大叔要不要办理其他业务。大叔说我再改一次密码。这时候总不能让大叔去队尾排队再改密码吧。嗯,大叔在这里做生意是个宏任务,存完钱要改密码,这就创建了一个微任务。如果大叔想做其他业务,就生成一个新的microtask,直到所有的microtask都执行完。团队中的下一个人又来了。这个团队是一个任务队列,staff是一个单线程的JS引擎。排队的人只能让他一件件给你办事。也就是说,执行当前macrotask中的所有microtask。以代码为例执行下一个宏任务输出结果是132,因为setTimeout是一个宏任务,即使它的时间为0,当前宏任务中的任务还没有执行,她插队也没用。即使定时时间为0,也是一个延迟任务,所以放在异步处理模块中,首先要注意:异步处理模块(延迟哈希表)是一个与任务队列同级的数据结构。每个宏任务结束后,主线程会查看延迟哈希表,将其中的过期任务取出并依次执行,如回调/定时器达到触发条件等。不明白的,下面有图,大家一看就很清楚了。输出结果为1342,当遇到微任务(这里是回调)时,会被放到微任务队列中,等待执行栈中的任务执行被处决,然后拿出来执行。看图片。你非要懂,她不懂,你可以看这张图一会儿。这部分也是面试爱问的问题。从图中可以看出,执行流程就形成了。一个循环,这就是事件循环(EventLoop)事件循环(EventLoop)事件循环:一句话,就是从入栈到出栈的循环:一个宏任务,所有微任务,渲染,一个宏任务,所有微任务,Rendering.....循环过程:所有同步任务在主线程上依次执行,形成一个执行栈(调用栈)。异步任务处理完毕后,放入一个任务队列中。当执行栈中的任务执行完后,去检查microtask队列中的microtask是否为空,有则执行。如果在microtask执行过程中遇到了microtask,则将其添加到microtask队列的尾部继续执行。所有微任务执行完毕后,去任务队列中查看宏任务是否为空。如果有则取出先进入队列的宏任务压入执行栈执行其同步代码,然后返回步骤2执行宏任务中的微任务,如此循环直到宏任务中的task也执行了,所以我们循环练习一下,把她理解透输出结果:promise1->promise2->script->promise3->setTimeout想想为什么?script是宏任务,先执行里面的微任务,遇到宏任务setTimeout放到异步处理模块(延迟哈希表)继续执行promise,printpromise1遇到循环,执行,遇到acallbackresolve(),上面说到callback属于microtask,放到microtask队列中继续执行,打印promise2继续执行,打印脚本执行栈的task完成,去microtask队列中获取一个然后callback、execute、printpromise3个微任务都执行完了,去task队列拿一个宏任务执行setTimeout。打印setTimeout不理解,遇到async/await时想一想?async/await是ES7引入的重大改进。它可以使用同步代码实现不阻塞主线程的异步访问资源的能力,让我们的代码逻辑更加清晰。说白了async:就是异步执行,隐式returnPromiseawait:返回的是一个Promise对象见titleasyncfunctionfun(){console.log(1)leta=await2console.log(a)console.log(3)}console.log(4)fun()console.log(5)outputresult:41523结合async/await的特点,我们用ES6functionfun(){returnnewPromise(()=>{console.log(1)Promise.resolve(2).then(a=>{console.log(a)console.log(3)})})}console.log(4)fun()console.log(5)上面说了,回调是微Task,所以直接丢进microtask队列等待。这道题自然是最后的执行。是不是更容易理解?不要看下面的答案。想想这个问题,和上面的functionbar(){console.log(2)}asyncfunctionfun(){console.log(1)awaitbar()console.log(3)}console.log(1)有点不同。log(4)fun()console.log(5)输出:41253为什么?上面的例子中,2没有打印出来,为什么会打印出这个,因为await的意思是等到await完成后再执行,所以"awaitbar()"是从右往左执行的,在执行bar()之后,和然后遇到await,返回一个microtask(即使这个task里面什么都没有),放入microtask队列,放弃主线程。上面说了async/await就是以同步的形式实现异步。同步是一次一个步骤。如果await没有在microtask队列中返回,那么自然就不能在await下执行了,导致3。最后下面有打印结合上面的问题,我就不写答案了。你可以解决她的asyncfunctionasync1(){console.log('async1start');等待async2();console.log('async1end')}asyncfunctionasync2(){console.log('async2')}console.log('scriptstart');setTimeout(function(){console.log('setTimeout')},0);async1();newPromise(function(resolve){console.log('promise1');resolve()}).then(function(){console.log('promise2')});控制台。log('scriptend')支持,手留余香,荣幸
