最近计划把Node整体过一遍,无意间看到Event Loop这一概念,不管在面试中还是在工作中,这一个概念还是应用的挺广泛,打算复盘一下。
单线程
无法避开不谈的是JS是单线程语言。什么线程呢?那么平时我们常说的进程是什么呢?
- 进程是操作系统分配资源和调度任务的基本单位
- 线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
即两者是包含关系。谈到单线程先说对应的多线程(譬如apache,tomcat),当客户端每发一个请求,服务器便起一个线程,往sql之类的数据库取目标数据,要知道IO操作切换是比较消耗性能(内存)和时间的,apache这边给出的优化方案便是线程池。那么如何两个线程同时访问一个资源怎么办?多线程的处理方案,是首先访问的那个先加个锁。然而这个方案对于浏览器起家的JS并不是那么使用,操作DOM元素为避免紊乱,单线程便成了首选。
在JS告诉发展的今天,像WebWork这样的多线程功能便衍生而出,那么JS变了么,从专一变成花心???哈哈,并没有!!!其实Web work并不能撼动JS单线程的本质,它有诸多限制:1、同源限制,必须与主线程文件同源;2、DOM限制,无法获取DOM对象;3、脚步限制,无法使用alert,confirm;4、无法获取本地资源只能来自网络;说白了就是完全是老大哥排忧解难的小老弟,对于页面DOM资源和方法是没有权限,分担些计算等的性能操作的负担。
浏览器模型

先贴张图,浏览器这里有很多东西讲,今天的主角是Event Loop,这边先简单描述一下
- 用户界面-包括地址栏、前进/后退按钮、书签菜单等
- 浏览器引擎-在用户界面和呈现引擎之间传送指令
- 呈现引擎-又称渲染引擎,也被称为浏览器内核,在线程方面又称为UI线程
- 网络-用于网络调用,比如 HTTP 请求 用户界面后端-用于绘制基本的窗口小部件,UI线程和JS共用一个线程
- JavaScript解释器-用于解析和执行 JavaScript 代码 数据存储-这是持久层。浏览器需要在硬盘上保存各种数据,例如 Cookie
任务队列
- 所有同步任务都在主线程上执行,形成一个执行栈
- 主线程之外,还存在一个任务队列。只要异步任务有了运行结果,就在任务队列之中放置一个事件。
- 一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
- 主线程不断重复上面的第三步。
Event Loop
主线程从任务队列中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)

emmmmm.....铺垫了这么久,这么寥寥几句就没有了,哈哈,并没有。我刚接触的时候就觉得,啊哈,还挺简单的。有一天,有个朋友问我你知道宏任务和微任务么?

任务(tasks,也叫macro-task)被放到任务源中,浏览器内部执行转移到JavaScript/DOM领域,并且确保这些 tasks按序执行。在tasks执行期间,浏览器可能更新渲染。来自鼠标点击的事件回调需要安排一个task,解析HTML和setTimeout同样需要。
微任务(Microtasks)队列通常用于存放一些任务,这些任务应该在正在执行的脚本之后立即执行,比如对一批动作作出反应,或者操作异步执行避免创建整个新任务造成的性能浪费。每次事件循环中,如果没有其他JavaScript运行并且任务(task)都执行完毕了,那么微任务就会在回调之后被执行。在微任务中排队的任何其他微任务将被添加到队列的末尾并进行处理。微任务包括 MutationObserver、Promise的回调(微任务包括:process.nextTick(Nodejs), Promises, Object.observe, MutationObserver;任务(tasks)包括:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering)。
说了这么多,贴一端执行顺序的代码,自我考察下吧,哈哈
// Let's get hold of those elements
var outer = document.querySelector('.outer');
var inner = document.querySelector('.inner');
// Let's listen for attribute changes on the
// outer element
new MutationObserver(function() {
console.log('mutate');
}).observe(outer, {
attributes: true
});
// Here's a click listener…
function onClick() {
console.log('click');
setTimeout(function() {
console.log('timeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise');
});
outer.setAttribute('data-random', Math.random());
}
// …which we'll attach to both elements
inner.addEventListener('click', onClick);
outer.addEventListener('click', onClick);