You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
setTimeout(function() {
console.log(4)
}, 0);
new Promise(function(resolve) {
console.log(1);
for (var i = 0; i < 10000; i++) {
i == 9999 && resolve()
}
console.log(2)
}).then(function() {
console.log(5)
}).then(function() {
console.log(6)
});
console.log(3);
out >> 1,2,3,5,6,4
out i think >> 3,4,1,2,5,6
本以为promise跟setTimeout是一样的,多是异步的,JS遇到这些代码,会先把回调函数注册到任务队列中, 等到js主线程执行完同步的代码后(也就是执行完console.log(3))再去执行刚才注册到任务队形的回调函数。但是从结果中可以看出Promise构造函数中的resolve函数的执行先于then的回调函数,then回调函数先于setTimeout回调,而又在console.log(3)后,也就是在JS主线程后。为什么会这样呢!
通过查看一些文章才明白,这些是跟JS event loop有关!上面的script(整体代码)以及其中的setTimeout都属于js event loop中macro-task, 而promise属于micro-task,micro-task的优先级要大于macro-task。
看到这里我相信有部分人会跟我一样懵B了, js event loop, macro-task, micro-task什么东东,没听说过。感觉自己真是too young too nactive! 好吧,废话说了这么多,进入正题~(以下内容仅属个人看法,有误之处希望批评指正)
最近在看到一道面试题时,困惑住了
out >> 1,2,3,5,6,4
out i think >> 3,4,1,2,5,6
本以为promise跟setTimeout是一样的,多是异步的,JS遇到这些代码,会先把回调函数注册到任务队列中, 等到js主线程执行完同步的代码后(也就是执行完console.log(3))再去执行刚才注册到任务队形的回调函数。但是从结果中可以看出Promise构造函数中的resolve函数的执行先于then的回调函数,then回调函数先于setTimeout回调,而又在console.log(3)后,也就是在JS主线程后。为什么会这样呢!
通过查看一些文章才明白,这些是跟JS event loop有关!上面的script(整体代码)以及其中的setTimeout都属于js event loop中macro-task, 而promise属于micro-task,micro-task的优先级要大于macro-task。
看到这里我相信有部分人会跟我一样懵B了, js event loop, macro-task, micro-task什么东东,没听说过。感觉自己真是too young too nactive! 好吧,废话说了这么多,进入正题~(以下内容仅属个人看法,有误之处希望批评指正)
JS Event loop
event loop(事件循环) 根据宿主环境可以分为浏览器下的,workers和nodeJS中的,本文主要是基于浏览器环境的。在这三个环境中,多是只能有一个event loop。
那event loop用来干什么呢?
主要用来实现js的异步机制,js的异步机制简单的说就是不停的循环(JS主线程->先执行同步的代码后->再从任务队列读取事件)的这一过程。
那为什么有这么个event loop呢?
那还不是因为JS一开始就是一门单线程语言,所以对于处理一些耗时比较长的任务,就会阻塞后面的任务,对不对,就像我们想浏览新闻,但是新闻包含的超清图片加载很慢,难道我们的网页要一直卡着直到图片完全显示出来。所以要变个法子来去阻塞化,要异步的去加载那些能阻塞主线程执行的代码。那怎么个异步法呢?
从上图中我们可以了简单描述这个event loop(事件循环):
主线程运行的时候,产生堆(内存分配发生的地方)和栈(函数调用时会形一个个栈帧,其实就是js的执行栈),主线程中同步的代码会进入栈中,当遇到setTimout, promise,用户交互、脚本、UI 渲染、网络请求等各种异步任务时,就将他丢给WebAPIs,接着执行同步任务,直到栈为空。
在此期间WebAPIs完成这个事件,把异步任务中回调函数放入CallbackQueue中等待;
当执行栈为空时,Event Loop把Callback Queue中的一个任务放入栈中,回到第1步。
这一下子我们对event loop有了个大概的了解。如果你想深入的了解event loop, 这里有几篇深入的文章:
Macro-task
其实上图中script(整体代码),DOM事件,AJAX, setTimout多属于Macro-task。
如果查看了HTML规范,发现Macro-task其实就是event loop里的task, 而HTML规范中的task是这么定义它的:
一个eventloop有一或多个task队列(上图中的第2步的异步任务其实是放到task队列中,wepAPI就是每个task关联的document)
一个task队列是一列有序的task,而且是一个先进先出的队列,主要用来做以下工作:Events task,Parsing task, Callbacks task, Using a resource task, Reacting to DOM manipulation task等
每个task都有自己相关的document,比如我们发起AJAX异步请求时,进入到task队列,这时document就是xmlhttprequest, 其实也就是上图中的webAPI.
每个task由一个确定的task source提供, task source包括如下:
而且从同一个task source来的task必须放到同一个task队列中,从不同源来的则被添加到不同队列中
各个task source的优先级有可能是不一样的,比如会为鼠标键盘事件(用户交互)设置更高优先级,这样会使用户感觉流畅.在、再比如自己遇到一个坑:
有了上面的定义后,当主线程运行到DOM事件,AJAX, setTimeout时,发现你们原来是Macro-task, 不属于我的管辖区内,然后说等你们辖区(各自的web API)处理好你们再来通知道我(回调函数),于是就把他们扔到各自的管辖区内(任务队列)
Micro-task
一般来说,microtask 包括:
我们例子中的promise.then其实就是Mico-task.
了解概念性的东西,我们还是去查看HTML规范,从规范中我们可以大概了解到:
每个event loop 多会有一个micro-task队列。Micro-task 会排在 microtask 队列而非 Macro-task 队列中
当当前Macro-task执行完毕后,执行栈为空时(只剩global执行上下文时)会立刻先处理所有Micro-task队列中的任务, 按顺序全部执行,直到队列为空。如果 microtask 中又添加了新的 microtask,直接放进本队列末尾。当micro-task队列多已经执行完后并再去Macro-task队列中取出task去执行...如此反复的循环。Micro-task是跟在Macro-task末尾执行,像个小跟班。
为什么只有Promise.then是Micro-task, then中的回调是什么时候进入到Micro-task队列中的呢?而new Promise()构造函数中的resolve和reject函数为什么不是Micro-task呢?
这个要查看ES的规范我们明白下面几点:
Event loop的执行模型
通过上面的macro-task, micro-task定义我们知道:
现在我们结合macro,micro task 看看event loop是如何来执行一个任务的流程.
当主线程开始读取完代码(整体代码),执行栈为空时,开始依次执行:
通过这个简单的event loop 执行模型我们在来看最开始的例子:
参考:
The text was updated successfully, but these errors were encountered: