前言
在正式学习JS事件循环(Event loop)之前,我们需要掌握一些基础的浏览器和JS线程的知识。
我们都知道,我们写完的JS代码最终是被打包到浏览器上去执行的,那么浏览器做的事情,只有执行代码吗?答案当然是否定的,浏览器要做的事情很多。他要去解析JS,要去渲染页面,要运行第三方插件等等。由此可见浏览器是多进程的。
浏览器进程
- Browser进程
- CPU进程
- 第三方插件进程
这里面跟我们今天要讲的内容有关系的就是CPU进程。
一个进程里面可以有多个线程。根据前端特性可以将CPU进程分为:
- GUI线程(用于渲染页面)
- JS线程(用于执行JavaScript代码)
- 事件触发线程(管理任务队列,和EventLoop密切相关)
- 定时触发器线程
- 异步HTTP请求线程
事件循环(Event loop)
简单理解事件循环就是先确定事件的执行规则,然后按照这个规则循环执行。此时会涉及到宏任务与微任务的概念,后面着重讲解,先说事件循环。
JS是单线程执行的,就好比打电话,必须要打完当前这个电话挂断之后才能继续接打下一个,同理JS在执行代码任务时也是这样一个接一个的执行的。
但是如果一个任务的执行时间很长,它后面的所有任务岂不是就会卡住了嘛。就会造成一个问题,假如我想浏览一个新闻,这个新闻页面里包含了超高清的视频,难道我们的网页要等到这个视频完全加载完成之后再显示出来吗?
当然不是,相信大家一定见过,一个页面文字先出,图片后出来的情况。究其原因,就是将任务分成了两种类型:
- 同步任务
- 异步任务
- PS:关于同步、异步的解释可以去看另一篇文章: 【前端面试、Generator和async/await
当我们打开网站的时候,网页的渲染过程就是一大堆同步任务的,比如页面元素的渲染。而像加载图片、视频这种资源大、耗时久的动作就是异步任务。下面我就来弄清楚执行机制是什么?
先看一段代码:
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
正确的打印顺序是:script start, script end, promise1, promise2, setTimeout。
相信很多人看到这已经开始迷惑了,为什么是这样的呢?
- 如下导图(此图从网站下载)
解释一下:
- 任务和异步任务会分别进入不同的执行空间,同步任务进入主线程优先执行,异步的进入Event table,并注册该异步动作的回调函数。
- 当异步动作中指定的事情完成之后(比如发送请求给服务端),Event Table会将这个函数移入Event Queue(事件队列)
- 主线程内的任务执行完成之后,会有一段空置时间(很短),这个时候会去看Event Queue(事件队列)中找函数,如果有Event Queue中有需要执行的函数,那么就会把这个函数放入主线程进行执行。
- 以上的过程不断重复,形成了我们常说的Event loop(事件循环) 看代码:
let data = [];
$.ajax({
url:www.javascript.com,
data:data,
success:() => {
console.log('发送成功!');
}
})
console.log('代码执行结束');复制代码
上面是一段简易的ajax请求代码:
- ajax进入Event Table,注册回调函数success。
- 执行console.log('代码执行结束')。
- ajax事件完成,回调函数success进入Event Queue。
- 主线程从Event Queue读取回调函数success并执行。
相信通过上面的文字和代码,你已经对js的执行顺序有了初步了解。
微任务(Microtasks)、宏任务(task)?
微任务和宏任务皆为异步任务,它们都属于一个队列,主要区别在于他们的执行顺序,Event Loop的走向和取值。那么他们之间到底有什么区别呢?
由此可见,JS线程在执行代码时正确的顺序是:同步任务--> 微任务--> 宏任务。 一个方法执行时,会向执行栈中加入这个方法的执行环境(也就是整个函数的代码),在这个执行环境里面调用其他方法或者是自己时,对于JS线程而言,无非是再添加一个执行环境而已。这个动作可以一直循环到栈溢出,也就是达到了内存最大值。
宏任务一般指:整体代码script、setTimeout、setInterval、setImmediate。 微任务一般指:原生Promise、process.nextTick、MutationObserver。
其他更加详细的解释可以参考文章: JS事件循环机制(event loop)之宏任务/微任务