JS事件循环机制
一、为什么JavaScript是单线程?
JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。
JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点就会发生冲突。
二、执行栈
当我们调用一个函数时生成这个函数相对应的执行环境,也叫执行上下文,多个函数执行上下文就需要执行栈来管理。JS 中的执行栈首先会产生一个全局执行上下文并压入执行栈,每遇到一个函数调用,就会往栈中压入一个新的上下文。JS引擎执行栈顶的函数,执行完毕,弹出当前执行上下文。
三、任务队列
单线程意味着任务需要排队,一个任务执行完毕后才可以执行下一个任务。但由于类似AJAX网络请求、setTimeout时间延迟等可能造成长时间的等待,因此通过将任务交给相应的异步模块去处理,而继续执行执行栈中的其他任务,等异步任务返回结果后,再按照顺序将回调函数加入到与执行栈不同的另一个队列等待执行,这个队列也就是任务队列。
同步任务和异步任务
同步任务指的是排队顺序执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不直接进入执行栈、而进入"任务队列"(task queue)的任务,包括AJAX请求、定时器、DOM操作等。
宏任务和微任务
任务队列根据宏任务和微任务又分为宏任务队列和微任务队列。宏任务包括script(代码整体)、setTimeout、setInterval、IO、UI rendering。微任务包括process.nextTick、Promise.then、Observer等。
setTimeout、Promise等为任务源,进入任务队列的是他们的执行任务(如回调函数)。
四、事件循环
事件循环过程:
所有同步任务都在主线程上形成一个执行栈进行执行。
JS执行到异步任务时会挂起,当等待的事情完成时,将这个任务的回调函数移入到任务队列中。
一旦执行栈中的所有同步任务执行完毕则检查任务队列中是否有任务,若存在则进入执行栈,开始执行。
上面的三步不断重复,这个过程被称为“Event Loop 事件循环”。
示例代码1
console.log(‘Hi‘); //同步任务 setTimeout(function cb1() { //异步任务 console.log(‘cb1‘); }, 5000); console.log(‘Bye‘); //同步任务//Hi Bye cb1
简而言之,先执行同步事件、异步事件挂起;本轮同步事件执行完毕,依次执行异步事件回调函数(达到条件的);如果再次产生异步事件则循环;
示例代码2
console.log(‘1‘) // 这是一个同步任务 ? setTimeout( // 这是一个异步任务 function a() { console.log(‘2‘) // 这是一个同步任务 setTimeout( // 这是一个异步任务 function a2() { console.log(‘3‘) // 这是一个同步任务 },5000); },5000); ? console.log(‘4‘) // 这是一个同步任务 // 1 4 2 3
示例代码3
setImmediate(function a() { //异步任务、宏任务 console.log(1); }, 0); setTimeout(function b() { //异步任务、宏任务 console.log(2); }, 0); new Promise(function (resolve) { //同步任务 console.log(3); resolve(); console.log(4); }).then(function c() { //微任务、异步任务 console.log(5); }); console.log(6); //同步任务 console.log(8); //同步任务 ? //3 4 6 8 5 1 2
1.同步任务直接执行,打印3 4 6 8
2.执行本轮任务队列中的所有微任务,打印5
3.执行本轮任务队列中的宏任务,打印1 2
即先执行同步任务,再执行任务队列中的微任务,然后执行任务队列中的宏任务。
示例代码4
console.log(‘1‘); //同步任务 ? setTimeout(function a() { //异步任务、宏任务 console.log(‘2‘); }, 0); ? new Promise(function (resolve) { //同步任务 console.log(3); resolve(); console.log(4); }).then(function b() { //微任务、异步任务 console.log(5); }); ? new Promise(function (resolve) { //同步任务 console.log(6); resolve(); console.log(7); }).then(function c() { //微任务、异步任务 console.log(8); }); ? console.log(‘9‘); //同步任务 ? //1 3 4 6 7 9 5 8 2
1.同步任务之间执行,打印 1 3 4 6 7 9
2.执行本轮任务队列中的所有微任务,打印5 8
3.执行本轮任务队列中的宏任务,打印2
五、总结
JS事件循环机制:
先执行同步任务,再执行异步任务(并且先执行微任务,再执行宏任务),依次循环;
先执行宏任务再执行微任务,依次循环;