前言
The last time, I have learned
【THE LAST TIME】一直是我想写的一个系列,旨在厚积薄发,重温前端。
也是给自己的查缺补漏和技术分享。
欢迎大家多多评论指点吐槽。
系列文章均首发于公众号【全栈前端精选】,笔者文章集合详见Nealyang/personalBlog。目录皆为暂定
执行 & 运行
首先我们需要声明下,java script
的执行和运行是两个不同概念的,执行,一般依赖于环境,比如 node
、浏览器、Ringo
等, java script 在不同环境下的执行机制可能并不相同。而今天我们要讨论的 Event Loop
就是 java script
的一种执行方式。所以下文我们还会梳理 node
的执行方式。而运行呢,是指java script 的解析引擎。这是统一的。
关于 java script
此篇文章中,这个小标题下,我们只需要牢记一句话: java script 是单线程语言 ,无论HTML5
里面 Web-Worker
还是 node 里面的cluster
都是“纸老虎”,而且 cluster
还是进程管理相关。这里读者注意区分:进程和线程。
既然 java script
是单线程语言,那么就会存在一个问题,所有的代码都得一句一句的来执行。就像我们在食堂排队打饭,必须一个一个排队点菜结账。那些没有排到的,就得等着~
概念梳理
在详解执行机制之前,先梳理一下 java script
的一些基本概念,方便后面我们说到的时候大伙儿心里有个印象和大概的轮廓。
事件循环(Event Loop)
什么是 Event Loop?
其实这个概念还是比较模糊的,因为他必须得结合着运行机制来解释。
java script
有一个主线程 main thread
,和调用栈 call-stack
也称之为执行栈。所有的任务都会放到调用栈中等待主线程来执行。
暂且,我们先理解为上图的大圈圈就是 Event Loop 吧!并且,这个圈圈,一直在转圈圈~ 也就是说,java script
的 Event Loop
是伴随着整个源码文件生命周期的,只要当前 java script
在运行中,内部的这个循环就会不断地循环下去,去寻找 queue
里面能执行的 task
。
任务队列(task queue)
task
,就是任务的意思,我们这里理解为每一个语句就是一个任务
console.log(1);
console.log(2);
如上语句,其实就是就可以理解为两个 task
。
而 queue
呢,就是FIFO
的队列!
所以 Task Queue
就是承载任务的队列。而 java script
的 Event Loop
就是会不断地过来找这个 queue
,问有没有 task
可以运行运行。
同步任务(SyncTask)、异步任务(AsyncTask)
同步任务说白了就是主线程来执行的时候立即就能执行的代码,比如:
console.log('this is THE LAST TIME');
console.log('Nealyang');
代码在执行到上述 console
的时候,就会立即在控制台上打印相应结果。
而所谓的异步任务就是主线程执行到这个 task
的时候,“唉!你等会,我现在先不执行,等我 xxx 完了以后我再来等你执行” 注意上述我说的是等你来执行。
说白了,异步任务就是你先去执行别的 task,等我这 xxx 完之后再往 Task Queue 里面塞一个 task 的同步任务来等待被执行
setTimeout(()=>{
console.log(2)
});
console.log(1);
如上述代码,setTimeout
就是一个异步任务,主线程去执行的时候遇到 setTimeout
发现是一个异步任务,就先注册了一个异步的回调,然后接着执行下面的语句console.log(1)
,等上面的异步任务等待的时间到了以后,在执行console.log(2)
。具体的执行机制会在后面剖析。
- 主线程自上而下执行所有代码
- 同步任务直接进入到主线程被执行,而异步任务则进入到
Event Table
并注册相对应的回调函数 - 异步任务完成后,
Event Table
会将这个函数移入Event Queue
- 主线程任务执行完了以后,会从
Event Queue
中读取任务,进入到主线程去执行。 - 循环如上
上述动作不断循环,就是我们所说的事件循环(Event Loop
)。
小试牛刀
ajax({
url:www.Nealyang.com,
data:prams,
success:() => {
console.log('请求成功!');
},
error:()=>{
console.log('请求失败~');
}
})
console.log('这是一个同步任务');
- ajax 请求首先进入到
Event Table
,分别注册了onError
和onSuccess
回调函数。 - 主线程执行同步任务:
console.log('这是一个同步任务');
- 主线程任务执行完毕,看
Event Queue
是否有待执行的 task,这里是不断地检查,只要主线程的task queue
没有任务执行了,主线程就一直在这等着 - ajax 执行完毕,将回调函数
push
到Event Queue
。(步骤 3、4 没有先后顺序而言) - 主线程“终于”等到了
Event Queue
里有task
可以执行了,执行对应的回调任务。 - 如此往复。
宏任务(MacroTask)、微任务(MicroTask)
java script
的任务不仅仅分为同步任务和异步任务,同时从另一个维度,也分为了宏任务(MacroTask
)和微任务(MicroTask
)。
先说说 MacroTask
,所有的同步任务代码都是MacroTask
(这么说其实不是很严谨,下面解释),setTimeout
、setInterval
、I/O
、UI Rendering
等都是宏任务。
MicroTask
,为什么说上述不严谨我却还是强调所有的同步任务都是 MacroTask
呢,因为我们仅仅需要记住几个 MicroTask
即可,排除法!别的都是 MacroTask
。MicroTask
包括:Process.nextTick
、Promise.then catch finally
(注意我不是说 Promise)、MutationObserver
。
浏览器环境下的 Event Loop
当我们梳理完哪些是 MicroTask
,除了那些别的都是 MacroTask
后,哪些是同步任务,哪些又是异步任务后,这里就应该彻底的梳理下java script 的执行机制了。
如开篇说到的,执行和运行是不同的,执行要区分环境。所以这里我们将 Event Loop
的介绍分为浏览器和 Node 两个环境下。
先放图镇楼!如果你已经理解了这张图的意思,那么恭喜你,你完全可以直接阅读 Node 环境下的 Event Loop
章节了!
setTimeout、setInterval
setTimeout
setTimeout
就是等多长时间来执行这个回调函数。setInterval
就是每隔多长时间来执行这个回调。
let startTime = new Date().getTime();
setTimeout(()=>{
console.log(new Date().getTime()-startTime);
},1000);
如上代码,顾名思义,就是等 1s