目录
参考链接:
一、JavaScript是单线程语言:
JavaScript语言最大的特点就是单线程,即同一时间只能做一件事情,做完一件事情之后才能去做另一件事情。
例如,大家在银行排队取钱,一个窗口工作人员一次只能为一个客户服务,不能同一时间为多个顾客服务,负责就乱套了。JavaScript的单线程与它的用途有密切的关系。作为浏览器脚本语言,JavaScript的主要用途是与用户之间进行交互,以及操作DOM。这决定了它只能是单线程,否则就会带来复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM上添加节点,另一个线程删除节点,这时候浏览器以哪个线程为准来进行工作呢?
所以,为了避免同步的复杂性,从一诞生JavaScript就是单线程,这已经成为了这门语言的核心特征。为了利用多核CPU的计算能力,HTML5提出了Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程的控制,并且不可以操作DOM。所以一切JavaScript版的“多线程”都是通过单线程模拟出来的,这个新标准并没有改变JavaScript单线程的本质。
二、JavaScript的事件循环机制:
单线程就意味着所有任务需要排队,前一个任务结束才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等待。我们在浏览页面网站的时候,有一些图片是要通过数据请求之后才能加载显示的,那么我们整个页面是不是要等某一个比较大的图片加载完成之后整个网站才能显示出来呢?这样做显然是不行的,所以JavaScript语言的设计者意识到了这一点,于是将所有的任务分成了两种:
同步任务(synchronous):
同步任务指的是:在主线程上排队执行的任务,只有前一个任务执行完毕才能执行后一个任务。所有的同步任务都在主线程上执行,形成了一个执行栈。执行栈的执行顺序是后进先出。
异步任务(asynchronous):
异步任务指的是:不进入主线程,而是进入“任务队列”的任务。只有“任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
事件循环过程:
- 同步任务和异步任务分别进行不同的场所,同步任务进入主线程,异步任务进入事件表(Event Table)并且注册函数。
- 当指定的事情完成之后(例如接受到了Promise对象的返回结果resolve/reject),事件表(Event Table)会将这个函数移入到任务队列(Event Queue)。
- 主线程内的任务执行完毕之后,回去任务队列(Event Queue)读取对应的函数,移入到主线程执行。
- 上述过程不断重复,这就是事件循环(Event Loop)
三、宏任务和微任务:
如果有同学对于定时器不是很了解的话,可以先了解一下:定时器参考。如果对于Promise相关内容不是很了解的话,可以先了解一下:Promise(菜鸟教程)和 Promise(ECMA6入门)。
宏任务(macro-task)包含:整体代码script、setTimeout函数、setInterval函数。微任务(micro-task)包含:Promise、process.nextTick。
事件循环的顺序决定了JavaScript代码的执行顺序。首先进入整体代码script对应的脚本文件之后,开始第一次的循环。接着执行所有的微任务,然后再次从宏任务开始执行,找到对应的任务队列执行完毕,再次执行所有的微任务,以此循环。
在执行宏任务或者微任务的过程中,里面的微任务的优先级是大于宏任务的,也就是先执行微任务再执行宏任务。
示例:
分析一下代码段的执行过程,并写出输出结果:
setTimeout(()=>{
console.log("定时器");
});
new Promise(function(resolve, reject) {
console.log("Promise");
resolve();
}).then(function() {
console.log("Promise回调");
});
console.log("结束");
- 首先进入整个js文件,是一个宏任务。
- 遇到setTimeout宏任务,这是一个异步任务,进入事件表(Event Table)并注册函数,等定时结束之后将函数移入到任务队列(Event Queue)
- 遇到 new Promise执行同步代码console.log("Promise"),输出Promise。接收到resolve的返回结果,遇到then回调微任务,进入事件表(Event Table)并注册函数,然后将这个函数移入到任务队列(Event Queue)
- 遇到console.log("结束")。输出结束
- 一轮宏任务处理完毕,判断是否有微任务需要处理。有then回调微任务,输出Promise回调
- 一轮事件循环结束,先找到setTimeout宏任务执行,输出定时器,再继续寻找是否有微任务?没有,开始下一轮事件循环。是否有宏任务可以进行循环?没有,事件循环结束。