同步任务和异步任务(微任务和宏任务)转自掘金,作者认可JavaScript是一种单线程语言,分为同步任务和异步任务Synchronoustasks是指主线程中排队执行的任务,只有上一个任务完成后才能执行下一个任务。异步任务是指不进入主线程而是进入“任务队列”的任务;只有当主线程任务全部执行完毕后,“任务队列”中的任务才会进入主线程执行。异步任务分为宏任务和微任务。newpromise()和console.log()是同步任务。宏任务(macrotasks)和微任务(microtasks)由宿主(Node、浏览器)JS引擎发起。可以理解为外层同步代码)2.setTimeout/setInterval3.UI渲染/UI事件4.postMessage,MessageChannel5.setImmediate,I/O(Node.js)1.Promise2.MutaionObserver3.Object.observe(alreadyObsolete;replacedbyProxyobject)4.process.nextTick(Node.js)谁先跑,然后先跑,会不会触发新一轮的Tick?会不会执行流程:同步任务—>微任务—>宏任务先执行所有同步任务,当异步任务放入任务队列,同步任务执行完后,开始执行当前所有的异步任务。先执行任务队列中的所有microtask,然后执行一个macrotask,再执行所有microtask,再执行一个macrotask。然后执行所有微任务……以此类推,直到执行结束。3-6的循环称为事件循环EventLoop事件循环是JavaScript实现异步的一种方法,也是JavaScriptasync/await的执行机制(强调)(个人注:async/await底层仍然是Promise,所以它是一个microtask,但是await比较特殊)async当我们在一个函数之前使用async时,该函数返回一个Promise对象asyncfunctiontest(){return1//async函数会帮助我们使用Promise.resolve(1)}//相当于下面的代码functiontest(){returnnewPromise(function(resolve,reject){resolve(1)})}//可以看出async只是一个语法糖,只是帮我们返回一个Promiseawaiwait意思是等待,也就是右边那个“表达式”的结果。这个表达式的计算结果可以是一个Promise对象的值,也可以是一个函数的值(也就是说没有特别的限制)。而且它只能在内部与异步一起使用。使用await时,会从右向左执行。遇到await时,★★★★★会在函数内部阻塞后面的代码去执行函数外的同步代码,执行完外部同步代码后,返回函数执行剩余代码★★★★★,而当await执行完成后,会先处理microtask队列的代码示例//1console.log('1');//2setTimeout(function(){console.log('2');process.nextTick(function(){console.log('3');})newPromise(function(resolve){console.log('4');resolve();}).then(function(){console.log('5')})})//3process.nextTick(function(){console.log('6');})//4newPromise(function(resolve){console.log('7');resolve();}).then(function(){console.log('8')})//5setTimeout(function(){console.log('9');process.nextTick(function(){console.log('10');})newPromise(function(resolve){console.log('11');resolve();}).then(function(){console.log('12')})})//先执行1,输出1//执行到2,将setTimeout放入异步任务队列(宏任务)//执行到3。Putprocess.nextTickintotheasynchronoustaskqueue(microtask)//执行到4,上面说的promise是同步任务,所以输出7,然后把then放到异步任务队列(microtask)Task)//执行到5,同2//以上同步任务全部完成,开始异步任务//先执行microtask,发现里面有两个microtask,分别被3和4、所以输出为68//执行另一个宏任务,即第一个setTimeout//先输出2,将process.nextTick放入微任务,然后输出4如上promise,再将then放入微任务//再次执行Somicrotaskoutputoutput35//类似的,执行另一个macrotasksetTImeout2,输出911然后执行microtaskoutput1012//所以最好的顺序是:176824359111012asyncfunctionasync1(){console.log('async1start')awaitasync2()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')})console.log('scriptend')//先执行同步代码,console.log('scriptstart')//遇到setTimeout,会被推入宏任务队列//执行async1(),也是同步的,但是返回值是Promise,内部先执行console.log('async1start')//再执行async2(),然后打印console.log('async2')//会从执行从右到左,遇到await会阻塞后面的代码,同步代码会在外部执行//进入newPromise,printconsole.log('promise1')//will.then被放入事件循环的microtask队列//继续执行,打印console.log('scriptend')//外部同步代码执行完毕,然后回到async1()内部,继续执行awaitasync2()后面的代码,执行console.log('async1end'),所以打印出async1end(个人理解:async/await本质上也是Promise,也属于microtasks,所以遇到await时,await后面的代码是阻塞的,应该也放在microtask队列中。同步代码执行完后,然后执行microtask队列的代码,当执行microtask队列的代码时,也是按照压入microtask队列的顺序执行)//执行microtask队列的代码,打印console.log('promise2')//进入第二个事件循环,执行宏任务队列,打印console.log('setTimeout')/***执行结果为:*scriptstart*async1start*async2*promise1*scriptend*async1end*promise2*setTimeout*/console.log(1);asyncfunctionfn(){console.log(2)newPromise((resolve)=>{resolve();}).then(()=>{console.log("XXX")})awaitconsole.log(3)console.log(4)}fn();newPromise((resolve)=>{console.log(6)resolve();}).然后(()=>{console.log(7)})console.log(8)//执行结果为:12368XXX47/*前面的12368不再解析,重点在后面的XXX47,可以看出代码console.log(4)awaitconsole.log(3)被放入微任务队列后,代码console.log("XXX")也被压入微任务队列,console.log("XXX")在console.log(4),所以在执行完同步任务后执行微任务队列代码时,先打印XXX,再打印4。*/console.log(1);asyncfunctionfn(){console.log(2)awaitconsole.log(3)awaitconsole.log(4)awaitconsole.log("afterawait:",11)awaitconsole.log("Afterawait:",22)awaitconsole.log("Afterawait:",33)awaitconsole.log("Afterawait:",44)}setTimeout(()=>{console.log(5)},0)fn();newPromise((resolve)=>{console.log(6)resolve();}).then(()=>{console.log(7)})console.log(8)/***执行结果为:*1*2*3*6*8*4*7*Afterawait:11*Afterawait:22*Afterawait:33*Afterawait:44*5*//*可以看出,代码执行时,只要遇到await,就会执行当前的await,并将await之后的代码放入microtask队列中。但是定时器中的5是最后打印出来的。可以看出,当连续遇到await时,将await之后的代码不断放入microtask队列,代码执行顺序会先执行microtask队列,然后才会执行。宏任务队列里面的代码。*/Promise.resolve().then(()=>{console.log(0);returnPromise.resolve(4)//如果返回4,则扩展2位,然后打印0,1,4,2,3,5,6,7}).then(res=>console.log(res))Promise.resolve().then(()=>{console.log(1);}).then(()=>{console.log(2);}).then(()=>{console.log(3);}).then(()=>{console.log(5);}).then(()=>{console.log(6);}).then(()=>{console.log(7);})/*这道题的重点是在原生Promise的then方法中,如果一个普通的值被返回,返回值将被立即调用并分配给resolve函数。如果返回值为thenable,则then方法会被放入微队列中执行。如果返回值是一个Promise.resolve,它会被再次添加。微任务队列。也就是说,微任务向后移动。Promise.resolve本身执行then方法,then方法本身在微任务队列中执行。同时在返回Promise.resolve时,resolve调用的返回值在上级then中作为resolve的参数传递,而调用外层的then方法本身在微队列中,所以执行顺序作用是将微队列向下移动两次。*/根据w3c的最新解释,每个任务都有一个任务类型。同一类型的任务必须在一个队列中,即有多个队列。不同类型的任务可以属于不同的队列。在二级事件循环中,浏览器可以根据实际情况从不同的队列中选择任务执行。浏览器必须准备一个微队列。微队列中的任务按优先级排列,其他任务全部执行。里面的东西都必须交给我,连绘图任务也不例外。等待是最优先的。随着浏览器复杂度的快速增加,W3C不再使用宏队列。目前chrome实现中至少包含了以下队列延迟队列:用于存放定时器到达后的回调任务,优先交互排队:用于存放用户操作后产生的事件处理任务,高优先级微队列:用户存放需要的任务执行速度最快,优先级最高。向微队列添加任务的主要方式主要是使用Promise、MutationObserver,例如://立即向微队列添加一个函数Promise.resolve().then(function)任务有优先级吗?任务没有优先级,在消息队列中先进先出但是消息队列有优先级//立即在微队列中添加一个函数并执行promise.resolve().then(function)setTimeOut(()=>{//第三步执行延迟队列中的任务console.log(1);},0)promise.resolve().then(()=>{//第二步执行微中的任务队列console.log(2);})console.log(3);//第一步执行全局js//321面试题1.如何理解JS的异步?JS是单线程语言,因为它运行在浏览器的渲染主线程上,渲染主线程只有一个。渲染主线程承担了很多工作,渲染页面和执行JS都在里面运行。如果使用同步的方式,很有可能主线程会被阻塞,导致消息队列中的很多其他任务无法执行。这样一来,繁忙的主线程一方面会白白浪费时间,另一方面无法及时更新页面,造成用户卡顿。所以浏览器使用异步的方式来避免。具体做法是当定时器、网络、事件监听等某些任务发生时,主线程会将任务交给其他线程处理,并立即自行结束任务的执行,然后执行后续代码.当其他线程执行完后,将提前传过来的回调函数包装成一个任务,加入到消息队列的尾部,等待主线程调度执行。在这种异步模式下,浏览器从不阻塞,从而最大程度保证了单线程的流畅运行。2.解释js的事件循环事件循环又叫消息循环,是浏览器渲染主线程的方式。在Chrome的源代码中,它启动了一个不会结束的for循环。每个循环从消息队列中取出第一个任务执行,其他线程只需要在适当的时候将该任务添加到队列尾部即可。过去,消息队列简单分为宏队列和微队列。这种说法已经不能满足复杂的浏览器环境,取而代之的是更加灵活多变的处理方式。根据W3C官方的解释,每个任务都有不同的类型,相同类型的任务必须在同一个队列中,不同的任务可以属于不同的队列。不同的任务队列有不同的优先级。在事件循环中,浏览器决定采用哪个队列。但是浏览器必须要有微队列,微队列的任务必须有最高的优先级,必须先被调度执行。3、JS中的定时器能否实现精准计时?为什么?不会,因为:计算机硬件没有原子钟,不可能做到准确计时。操作系统本身的计时功能存在少量偏差。由于JS定时器最终调用的是操作系统的函数,所以也带有这些偏差。根据W3C标准,浏览器在实现定时器时,如果嵌套层数超过5层,则其最小时间为4毫秒,当计时时间小于4毫秒时会出现偏差。受事件循环的影响,定时器的回调函数只能在主线程空闲时运行,从而引入另一个偏差
