深入了解JS 内部事件循环(event loop)机制

294 阅读4分钟

计算机系统的一种运行机制,JS用来解决单线程问题

一、问题

单线程语言,所有任务都在一个线程上完成,如果一旦遇到大量任务或者遇到一个耗时的任务,网页就会出现"假死“,无法响应用户行为

如果采用多线程,多线程需要共享资源、且有可能修改彼此的运行结果,对于一种网页脚本语言来说,这就太复杂了

二、解决方案

event loop:程序结构,用于等待和发送消息和事件

设置两个线程:一个主线程,负责程序本身的运行;一个event loop线程(消息线程),负责主线程和其它进程(主要是各种I/O操作)的通信

三、异步任务(宏任务、微任务

1、js引擎执行任务时,会先执行同步任务,同步任务执行完成后,再执行异步任务

2、执行异步任务时,会先执行微任务,再执行宏任务

在挂起任务时,JS 引擎会将所有任务按照类别分到这两个队列中,首先在 macrotask 的队列(这个队列也被叫做 task queue)中取出第一个任务,执行完毕后取出 microtask 队列中的所有任务顺序执行;之后再取 macrotask 任务,周而复始,直至两个队列的任务都取完

四、常见的宏任务

setTimeoutsetIntervalrequestAnimationFrame

对于setTimeout(fn,200),当到200ms时,fn会被放进“任务队列”,而“任务队列”必须要等到主线程已有的代码执行完才会执行fn,所以当程序执行到setTimeout(fn,200)这一行时,时间就开始计算,但是fn实际执行时并不一定是在200ms后,可能是在更久的时间后(取决于主线程上的同步代码的执行时间)

五、常见的微任务

MutationObserverPromise.then catch finally

六、完整步骤

1、主线程先执行同步任务,遇到异步任务被加入到响应的任务队列,如宏任务队列(macrotask)、微任务队列(microtask);

2、同步任务执行完毕后,调用栈stack被清空,开始执行异步任务,异步任务会优先执行微任务,取出微任务队列队首的任务加入到stack中,直到微任务队列执行完毕。如果执行微任务过程中又遇到新的微任务,会被加入到微任务队列的末尾,在该任务周期调用完成(如果执行微任务过程中又遇到新的微任务,会被加入到微任务队列的末尾,在该任务周期调用完成);

3、微任务队列执行完成后,stack清空,开始执行宏任务队列,宏任务队列中如果遇到微任务,则优先执行微任务;

4、UI rending,执行时间是在所有的微任务执行完成后,下一个宏任务执行之前调用UI rending

接下来让我们用一段代码来看看吧

console.log(1);  //同步任务
setTimeout(() =>{ //异步任务-宏任务
    console.log(2); //同步任务
    
    setTimeout(() =>{ //异步任务-宏任务
        console.log(14); //同步任务                       
        new Promise((resolve, reject) =>{ 
            console.log(15); //同步任务
            resolve(); //等价于promise().then()
        }).then(res =>{ //异步任务-微任务
            console.log(16); //同步任务              
        })
    }) 

    new Promise((resolve, reject) =>{ 
        console.log(3); //同步任务
        resolve(); //等价于promise().then()
    }).then(res =>{
        console.log(4);  //同步任务                    
    })
})

new Promise((resolve, reject) =>{ 
    resolve();  //等价于promise().then()
}).then(res =>{ //异步任务-微任务
    console.log(5); //同步任务
}).then(res =>{ //异步任务-微任务
    console.log(6); //同步任务
})

new Promise((resolve, reject) =>{ 
    console.log(7);  //同步任务
    resolve();//等价于promise().then()
}).then(res =>{   //异步任务-微任务
    console.log(8); //同步任务
}).then(res =>{  //异步任务-微任务
    console.log(9); //同步任务
})

setTimeout(() =>{ //异步任务-宏任务
    console.log(10); //同步任务
    new Promise((resolve, reject) =>{
        console.log(11); //同步任务
        resolve(); //等价于promise().then()
    }).then(res =>{ //异步任务-微任务
        console.log(12); //同步任务
    })
})

结果:1、7、5、8、6、9、4、2、3、4、10、11、12、13、14、15、16

总结:JS代码是从上往下执行的,遇到同步任务会先执行,遇到异步任务会放到队列中,队列先进先出,异步任务又分为微任务和宏任务,JS在执行时会先执行微任务,在执行宏任务