这是我参加「第四届青训营 」笔记创作活动的第2天
小白一枚,欢迎指正
话不多说,直接上题
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
要想了解答案,需求具备以下小知识,没明白的不妨往下挖挖 ^.^
一、V8 引擎内的 栈
也被称为“调用栈、履行栈”。栈是一种LIFO(last input first output)后进先出的数据结构。
当函数被推入履行栈,JS引擎就开端解析函数体,在堆里创建变量、把新的函数调用推入栈顶、或许把函数自身分发给Web API调用的专属容器。
当函数有了回来值,或许被分发到Web API容器,它就会被弹出栈,一起下一个函数调用会被推入栈顶。
假如JS引擎履行完一个函数,而且该函数没有清晰的指明回来值,JS引擎会默认的回来undefined
然后再将之弹出栈。人们通常说的JS同步运转指的便是JS引擎解析函数然后弹出栈(再运转下一个函数)的运转流程。简言之,在单线程下同一时刻只做一件事。
二、Web API
从栈发送到Web API容器内的Web API调用(比方事情监听函数、HTTP/AJAX恳求、或许是守时器函数)会一向在Web API容器内,直到触发操作停止。这些触发操作可能是一个点击事情被触发、或许是HTTP恳求完结从数据源获取数据、或许是守时器到达触发的时刻点,一旦到达触发条件,一个回调函数就会被推入第四个也是最终一个容器:回调行列。
回调行列将按增加的次序存储一切回调函数。当调用栈函数使命为空时,它会将行列最初的回调函数发送到调用栈。当调用栈函数使命再次清空时,它将发送下一个行列首位的回调函数。
回调行列是一种FIFO(first input first output)先进先出的数据结构。
三、烘托进程
WebAPI处理由栈中弹出后发送来的使命 所运用的线程和JS引擎所运用的的线程是不同的,浏览器有多个进程,其间烘托进程是我们首要关心的,里边又包含了多个线程。
页面的烘托,JS的履行,事情的循环,都在这个烘托进程内进行。
浏览器的烘托进程是多线程的, 首要包含的线程有:
- GUI烘托线程
- 担任烘托浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和制作等。
- 当界面需求重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会履行
- 留意,GUI烘托线程与JS引擎线程是互斥的,当JS引擎履行时GUI线程会被挂起(相当于被冻结了),GUI更新会被保存在一个行列中等到JS引擎闲暇时当即被履行。
- JS引擎线程
- 也称为JS内核,担任处理Javascript脚本程序。(例如V8引擎)
- JS引擎线程担任解析Javascript脚本,运转代码。
- JS引擎一向等候着使命行列中使命的到来,然后加以处理,一个Tab页(renderer进程)中无论什么时分都只有一个JS线程在运转JS程序
- 同样留意,GUI烘托线程与JS引擎线程是互斥的,所以假如JS履行的时刻过长,这样就会造成页面的烘托不连贯,导致页面烘托加载堵塞。
- 事情触发线程
- 归属于浏览器而不是JS引擎,用来控制事情循环(能够了解,JS引擎自己都忙不过来,需求浏览器另开线程协助)
- 当JS引擎履行代码块如setTimeOut时(也可来自浏览器内核的其他线程,如鼠标点击、AJAX异步恳求等),会将对应使命增加到事情线程中
- 当对应的事情符合触发条件被触发时,该线程会把事情增加到待处理行列的队尾,等候JS引擎的处理
- 留意,由于JS的单线程关系,所以这些待处理行列中的事情都得排队等候JS引擎处理(当JS引擎闲暇时才会去履行)
- 守时触发器线程
-
setInterval
与setTimeout
所在线程 - 浏览器守时计数器并不是由JavaScript引擎计数的,(由于JavaScript引擎是单线程的, 假如处于堵塞线程状况就会影响记计时的精确)
- 因而通过独自线程来计时并触发守时(计时结束后,增加到事情行列中,等候JS引擎闲暇后履行)
- 留意,W3C在HTML标准中规则,规则要求setTimeout中低于4ms的时刻间隔算为4ms。
-
- 异步http恳求线程
- 在XMLHttpRequest在衔接后是通过浏览器新开一个线程恳求
- 将检测到状况改变时,假如设置有回调函数,异步线程就发生状况改变事情,将这个回调再放入事情行列中。再由JavaScript引擎履行。
至此,我们能够得到整个使命履行流程,这个流程也叫事情循环
四、最终一步,宏使命和微使命
JS中分为两种使命类型:macrotask
和microtask
,在ECMAScript中,microtask称为jobs
,macrotask可称为task
-
macrotask(又称之为宏使命),能够了解是每次履行栈履行的代码便是一个宏使命(包含每次从事情行列中获取一个事情回调并放到履行栈中履行)
- 每一个task会自始至终将这个使命履行结束,不会履行其它
- 浏览器为了能够使得JS内部task与DOM使命能够有序的履行,会在一个task履行结束后,在下一个 task 履行开端前,对页面进行重新烘托
- (
task->烘托->task->...
)
-
microtask(又称为微使命),能够了解是在当时 task 履行结束后当即履行的使命
- 也便是说,在当时task使命后,下一个task之前,在烘托之前
- 所以它的响应速度比较setTimeout(setTimeout是task)会更快,由于无需等烘托
- 也便是说,在某一个macrotask履行完后,就会将在它履行期间发生的一切microtask都履行结束(在烘托前)
什么样的场景会形成macrotask和microtask呢?
- macrotask:主代码块,setTimeout,setInterval等(能够看到,事情行列中的每一个事情都是一个macrotask)
- microtask:Promise,process.nextTick等
macrotask和microtask是由什么控制保护的呢?
- macrotask中的事情都是放在一个事情行列中的,而这个行列由事情触发线程保护
- microtask中的一切微使命都是增加到微使命行列(Job Queues)中,等候当时macrotask履行结束后履行,而这个行列由JS引擎线程保护
五、代入解说答案
-
履行一个宏使命(栈中没有就从事情行列中获取)
这个时分打印出履行栈中的同步代码,也便是script start 和script end
-
履行过程中假如遇到微使命,就将它增加到微使命的使命行列中
这个时分,的确在过程中发现了微使命,也便是promise,将这个使命放到微使命行列,此时履行栈中只剩下一个守时器使命,这个使命不会立刻履行,他会被放入宏使命行列,履行栈闲暇
-
宏使命履行结束后,当即履行当时微使命行列中的一切微使命(顺次履行)
此时短暂闲暇的履行栈又插入promise,输出promise1和promise2,然后持续闲暇
-
当时宏使命履行结束,开端检查烘托,然后GUI线程接收烘托
-
烘托结束后,JS线程持续接收,开端下一个宏使命(从事情行列中获取)
由于履行栈闲暇,从宏使命行列中取出已经满足条件的守时器使命,这个时分输出了最终一行setTimeout
over
更多细节 推荐原文1,原文2