事件环EvenLoop相信大家应该不陌生,但是涉及的方面还挺多的,要掌握起来还是有一定难度,希望这篇总结能帮到你们~
EvenLoop
我们先以一段代码作为示例
console.log('start');
setTimeout(() => {
console.log('timeout');
});
Promise.resolve().then(() => {
console.log('resolve');
});
console.log('end');
按照js将代码按顺序压入执行栈来说,结果应该为:
start
timeout
resolve
end
不不不!既然今天我们要讲的是EvenLoop事件环,我们就知道没这么简单了,我们看看最后执行的结果为:
start
end
resolve
timeout
不要慌,接下来让我们一起探个究竟:
- 刚开始整个脚本作为一个宏任务来执行,对于同步代码直接压入执行栈 中
等等…宏任务?同步? 感到陌生没关系,我们先放下这道题,待会再回头分许。接下来我们一起学习一些概念
同步与异步
为什么要有异步?
我们知道js是单线程的,这意味着任务是一个接一个进行,可能遇到执行时间长的就会阻塞程序,效率太慢;这时就产生了异步任务,提高效率
概念
- 同步:一旦发出调用,就主动等待返回的结果,即同步任务是一个接一个执行
- 异步:调用者发出调用后,不会等待结果,而是将异步回调函数加入到任务队列中等待被执行
异步一般是指 网络请求、计时器、DOM事件监听
任务队列
什么是任务队列呢?
- js所有同步任务都在主线程上执行,形成一个执行栈
- 主线程之外还有一个任务队列。一旦执行栈中所有同步任务执行完毕,就会读取任务队列中的事件
- 主线程不断重复以上步骤
宏任务与微任务
在js中所有任务分为宏任务和微任务
- 宏任务:主代码块、定时器(setTimeout、setInterval)
- 微任务:Promise、process.nextTick等
每次执行栈的代码就是一个宏任务,包括任务队列中的任务,每执行宏任务之前浏览器都会重新渲染页面。
微任务
为什么要有微任务?什么时候执行?
解决异步回调问题
- 当产生了异步函数时,需要放入宏任务队列。此时需要等到前面所有宏任务执行完后,才能执行该回调,如果任务队列非诚长,就会造成应用卡顿
- 为了解决这个问题,在每一个宏任务中定义了一个微任务队列,执行完当前宏任务后,就会检查当前微任务队列,如果为空就执行下一个宏任务,否则先执行微任务
类似银行业务办理,每个人排队等待办理自己的业务就是一个宏任务。当我办理好银行卡后,想临时增加一个 办理套餐业务(微任务),此时不会重新叫号再排一次队,而是继续办理完成后,才轮到下一个(宏任务)
总结一下:
- 微任务在当前宏任务执行完后执行,浏览器重新渲染页面之前。
- 微任务:promise、process.nextNick
- 一般考察setTimeout和promise,此时promise优先执行
回到EvenLoop
有了前面知识的铺垫,我们对js执行有了一定了解,现在让我们总结一下
- JS引擎维护一个执行栈,同步代码会一次加入到执行栈中依次执行并出栈
- 遇到异步函数,将异步交给对应WebApi,继续执行后面的任务
- WebApi在条件满足时(如 触发点击事件),将对应的回调加入到任务队列中
- 执行栈为空时(即所有同步处理完成后),js引擎会去任务队列中取事件,加入到执行栈中执行
- 处理完成后出栈,重复上述操作,这就是事件循环EvenLoop机制
执行完宏任务时,要检查微任务队列,有则先执行,接着再执行浏览器UI线程的渲染工作
练习
我们先回到最开始的题目
console.log('start');
setTimeout(() => {
console.log('timeout');
});
Promise.resolve().then(() => {
console.log('resolve');
});
console.log('end');
相信不难分析:
- 先执行当前主线程的宏任务,即所有同步代码 打印start、end
- setTimeout放入宏任务队列
- Promise.then放入微任务队列
- 执行完宏任务,检查发现Promise.then,执行 打印 resolve
- 执行下一个宏任务 setTimeout 打印timeout
结果就很容易分析出来啦,不过这里要注意Promise本身属于同步代码,其promise.then才是异步操作
Promise
Promoise是为了解决回调地狱而产生,使用then方法链式编程。Promise意思是‘承诺’,可以当作一个容器,包含未来产生的结果,其结果有不同状态
Promise立即执行
新建一个Promose函数立即执行,因为Promise本身是同步的,其内部的then指定的回调函数才是异步,具体可以参考以下链接学习:
async/await
async表示该函数也是Promise异步函数,其await之前的代码也是立即执行,await后面的代码会执行,然后将结果放入任务队列中等待
- 再观察以下例子:
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0)
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
应该不难得出结果如下:
script start
promise1
script end
promise2
setTimeout
关于浏览器事件环讲解到此结束,如有错漏,欢迎指正~