js之- 执行机制,同步异步 和 微任务宏任务

    技术2022-07-15  79

    同步异步

    众所周知,javascript是单线程脚本语言。 也就说,javascript执行代码时,从上至下依次执行。

    同步: 指在主线程上,依次从上到下执行任务,上一个任务执行完毕,才会执行下一个任务 异步: 指在主线程上,执行任务时,发现如:ajax,setTimeout等异步代码时,将它们移除主线程,放入等待任务队列中,主线程执行完毕时,会把任务队列中的异步回调推入主线程执行。

    在主线程执行时,同步和异步在不同执行场所。 同步放在主线程,按照顺序依次执行。 异步进入Event Table执行并注册函数,异步任务完成,推入Event Queue。 当主线程任务全部执行完毕,检测到Event Queue有等待调用函数,推入主线程执行。 上述事件不断重复,就是事件循环Event Loop

    异步 又分为:宏任务(macrotask)和微任务(microtask)

    宏任务:javascript,setTimeout,ajax,setInterval等 微任务:Promise.then等

    宏任务需要多次Event Loop(事件循环)执行完。 微任务一次性执行完。

    执行顺序:遇到宏任务,先执行宏任务,放入对应Event Queue。遇到微任务,再执行微任务,放到微任务的Event Queue。在代码走完后,先从微任务的Event Queue回调里取出事件,直到没有微任务时,去执行宏任务。如下图

    代码执行顺序1:主线程 ⇒ 微任务 ⇒ 宏任务

    主线程也是一个大宏任务。如果微任务中有宏任务,放入宏任务,继续执行微任务

    代码执行顺序2:主线程 ⇒ 微任务 ⇒ 宏任务 ==> 宏任务中的微任务 ==> 宏任务

    如果宏任务中有微任务,把微任务放入微任务队列,当前的宏任务执行完毕后,查看是否有微任务,有则去在执行微任务,当微任务没有时,继续执行宏任务

    执行顺序总结:

    宏任务中的每一个事件走完后,都要去查看微任务是否存在,存在则去执行微任务,不存在则继续执行宏任务宏任务先进先出(进入宏任务时,已经先执行,待调用。不绝对,例如setTimeout的延时时间,最后有代码)

    执行顺序一的代码: 简单版

    console.log("start"); setTimeout(()=>{ console.log("time"); },0) new Promise((resolve)=>{ console.log("promise"); resolve(); }).then(()=>{ console.log("then"); }); console.log( "end" ); //执行结果: // start // promise // end // then // time 首先 执行主线程,依次从上往下,发现 console.log(“start”),打印 ‘start’发现 宏任务setTimeout,放入宏任务队列发现 Promise,执行代码打印 ‘promise’,Promise的resolve等才是异步,then回调是微任务,放入微任务队列发现 console.log( “end” ),打印 ‘end’主线程走完后,查看微任务是否有待执行,发现有Promise的回调then,执行then,打印 ‘then’微任务执行完毕后,查看宏任务,发现有setTimeout,打印 ‘time’最后打印的顺序为:start -> promise -> end -> then -> time

    执行任务二的代码: 稍复杂点版

    console.log("start"); setTimeout(()=>{ console.log("time1"); new Promise((resolve)=>{ console.log("promise2"); resolve() }).then(()=>{ console.log("then2"); }).then(()=>{ console.log("then3"); setTimeout(()=>{ console.log("time3"); },0); }) },0) new Promise((resolve)=>{ console.log("promise1"); resolve(); }).then(()=>{ console.log("then1"); setTimeout(()=>{ console.log("time2"); },0) }); console.log( "end" ); // 执行结果: // start // promise1 // end // then1 // time1 // promise2 // then2 // then3 // time2 // time3 先执行主线程,遇到 console.log(‘start’),打印 ‘start’遇到 setTimeout,放入宏任务,继续往下执行遇到 Promise,先执行 console.log(‘promose1’),打印 ‘promise1’,执行resolve(),then回调放入微任务遇到 console.log(‘end’),打印’end’主线程走完,先找微任务,发现有Promise的回调,执行log,打印出 ‘then1’,继续执行,发现setTimeout(tiem2的),放入宏任务微任务执行完毕,开始执行宏任务因tiem1的setTimeout先进入,并且延时0s,已经执行完毕,所以先运行time1的setTimeout,遇到log,打印 ‘time1’继续执行,遇到Promise,执行log,打印 ”promise2‘,回调放入微任务time1执行完毕后,发现刚才有事件进入微任务,先去执行微任务,执行log,打印 ‘then2’ 和 ‘then3’,then3中的setTimeout(time3)放入宏任务10.微任务执行完毕,继续宏任务中的 time2宏任务,执行log,打印’time2’。检测微任务是否有任务没有微任务,继续宏任务 中的 time3的setTimeout,执行log,打印 ‘time3’最后打印结果为:start -> promise1 -> end -> then1 -> time1 -> promise2 -> then2 -> then3 -> time2 -> time3

    宏任务的setTimeout执行顺序的一个要注意点 代码一

    setTimeout(()=>{ console.log("time1"); },60); console.time("TIME"); let i = 0; while (i <= 99999999) { i++; }; console.timeEnd("TIME"); setTimeout(()=>{ console.log("time2") },0); // 打印结果; // TIME: 76.124267578125ms // time1 // time2 第一步: 异步代码,time1进入宏任务,延时60ms执行第二步:按照执行顺序,执行到循环,同步代码,耗时76ms第三步:异步代码,time2进入宏任务,延时0ms执行此时,time1已经执行,所以打印顺序为:time1 -> time2

    代码二

    setTimeout(()=>{ console.log("time1"); },100); console.time("TIME"); let i = 0; while (i <= 99999999) { i++; }; console.timeEnd("TIME"); setTimeout(()=>{ console.log("time2") },0); // 打印结果; // TIME: 76.124267578125ms // time2 // time1 这里的 time1 在宏任务里延时100ms再执行同步代码循环耗时:76mstime2 进入宏任务,延时0ms执行此时time1还未到达执行条件,所以打印顺序为: time2 -> time1
    Processed: 0.014, SQL: 9