这几在看Vue的源码与其相关的博客,看到了关于Vue异步更新策略和nextTick的诸多文章,奈何功力不够深厚,看的是有点蒙蔽。主要缘由是这些个模块,须要对JS的一些运行机制和Event Loop
(事件循环)有必定的了解,因而决定再一次深刻的去了解这些知识。前端
在后来几天的学习中,当下就是总结了这几个蒙蔽点(你可先尝试自我回答一下):面试
call stack
(执行栈)?task queue
(任务队列)?task/macrotask
)宏/(microtask
)微事件?当时在学习的过程当中,我就以下图这样的感受:ajax
带着上面这些个问题,因而开始这几天的展开式的学习,从Event Loop
的概念,在到浏览器层面的实际运行原理。浏览器
本篇参考诸多文章,借鉴了里面的不少原话,都在文章的末尾都一一列出了。性能优化
我猜大部分作前端的,都知道Event Loop
(事件循环)的概念。可是不少人,对它的了解很是的片面。要想知道这个概念到底是什么,就要浏览器是如何运行的提及。markdown
首先,浏览器是多进程执行的,可是对于咱们研究最重要的,就是浏览器多个进程中的渲染进程,在浏览器的运行中,每个页面都有独立渲染进程。这个进程分别由以下几个线程在工做:数据结构
上面这几个线程,保证咱们整个页面(应用)的完整运行。并发
JS引擎线程负责解析Javascript脚本,运行代码,V8引擎就是在这个线程上运行的。异步
call stack
(执行栈)。全部的任务(函数),最终都会进入这里来执行。task queue
(任务队列),一旦任务队列中有能够执行的函数,就会压入栈内执行。如今出现了两个词:call stack
(执行栈),task queue
(任务队列),这里先来解释一下,什么是执行栈。函数
栈是一个先进后出的数据结构,全部的函数都会逐一的进入到这里面执行,一旦执行完毕就会退出这个栈。
function fun3() { console.log('run'); throw Error('err'); } function fun2() { fun3(); } function fun1() { fun2(); } fun1(); 复制代码
这里我特地在fun3
抛出了一个异常,咱们来看一下浏览器的输出:
上面这列出来的一个个函数,就是一个执行栈。这里我用一个更详细的图解来表示一下执行栈的运行过程:
上面这个图解,是对执行栈运行过程的分布演示。这个执行栈,就是咱们JS真正运行的地方,函数的调用会在这里造成一个调用栈,在里面是一个个执行的,必须得等到栈顶的函数执行完毕退出,才能继续执行下面的函数。一旦这个栈为空,它就会去task queue
(任务队列)看有没有待执行的任务(函数)。
那么咱们常说的任务队列,究竟又是一个啥玩意呢?
首先,这里还要强调一下上面的提到的,一个页面的运行,是须要多个线程配合支持的。
我们常说的任务队列,就是由这个事件触发线程来维护的。当时,我看到这个就蒙蔽了……尼玛,JS不是单线程吗?这条事件触发线程是怎么回事?
JS的确是仍是单线程执行的。这个事件触发线程属于浏览器而不是JS引擎,这是用来控制事件循环,而且管理着一个任务队列(task queue),然而对自己的JS程序,没有任何控制权限,最终任务队列里的函数,仍是得交回执行栈去执行。
那么这个线程维护的这个task queue
到底是干吗的呢?
上面在说call stack
(执行栈)的时候,我们提到了,一旦执行栈里面被清空了,它就会来看任务队列中是否有须要执行的任务(函数)。这个任务队列可能存放着延期执行的回调函数,相似setTimeout
,setInterval
(并非说setTimeout和setInterval在这里面,而是他们的回调函数),还可能存放着Ajax请求结果的回调函数等等。
这里先看下具体代码:
console.log('1'); setTimeout(() => { console.log('2'); }, 1000); $.ajax(/*.....*/) 复制代码
如今咱们来图解一下,整个运行过程(图画的比较丑,别建议):
因此,如今应该明白call stack
(执行栈),task queue
(任务队列)是怎么一个工做状态了吧。这里说一句不专业的话,可是你能够这么去理解:
在浏览器环境下的JS程序运行中,其实并非单线程去完成全部任务的,如定时器的读数,http的请求,都是交给其余线程去完成,这样才能保证JS线程不阻塞。
上面咱们提到在执行setTimeout
和setInterval
的时候,若是让JS引擎线程去读数的话,必然会形成阻塞。这也是不符合实际需求的,因此这件读数的事情,浏览器把它交给了渲染进程中的定时器触发线程。
一旦,代码中出现timer
类型API,就会交给这个线程去执行,这样JS引擎线程,就能够继续干别的事情。等到时间一到,这个线程就会将对应的回调,交给事件触发线程所维护的task queue
(任务队列)并加入其队尾,一旦执行栈为空,就会拿出来执行。
可是这里要提一点,就算执行栈为空也不必定能立刻执行这个回调,由于task queue
(任务队列)中可能还有不少的待执行函数,因此定时器只能让它到了时间的加入到task queue
中,但不必定可以准时的执行。
这个线程就是专门负责http请求工做的。简单说就是当执行到一个http异步请求时,就把异步请求事件添加到异步请求线程,等收到响应(准确来讲应该是http状态变化),再把回调函数添加到任务队列,等待js引擎线程来执行。
这个线程要重点说一下。首先这个GUI渲染线程和JS引擎线程是互斥的,说白了就是这两个同一时间,只能有一个在运行,JS引擎线程会阻塞GUI渲染线程,这也是为何JS执行时间长了,会致使页面渲染不连贯的缘由。
经过上面的这些理论,脑海里应该大体知道浏览器层面的JS是如何去工做了的吧。如今应该能够回答一开上面提出的部分问题了:
call stack
(执行栈)?答案:执行栈是JS引擎线程(主线程)中的一个先进后出执行JS程序的地方。一次只容许一个函数在执行,一旦栈被清空,将会轮询任务队列,将任务队列中的函数逐一压如栈内执行task queue
(任务队列)?答案:任务队列是在事件触发线程中,一个存放异步事件回调的地方。当定时器任务,异步请求任务在其余线程执行完毕时,就会将加入队列的队尾,而后被执行栈逐一执行。OK,如今已经解决三个问题,接下来咱们继续解决剩下的三个问题。这个三个问题,就是从JS事件循环机制的角度来研究了。
我相信大部分搞前端的,都应该知道这玩意。可是,我发现并非每一个人都能说清楚这个东西。完全了解这个,对于咱们处理开发中许多异步问题和阅读源码,是不少有帮助的。
首先,咱们先开看一张图(此图出自于Event Loop的规范和实现):
我以为若是你看完了上面渲染进程相关知识,在看这个图,应该是能理解百分之70了吧,剩下百分之是由于里面出现了microtask queue
(微任务队列)和Promise
,mutation observer
的相关字眼。
我以为,在开始了解Event Loop
以前,有必要提出两个问题:
Event Loop
?Event Loop
的每个循环,干了些什么事?针对上面给出这个图出现的一个新词microtask
,来展开进行学习。
首先,先来看一段代码:
setTimeout(() => { console.log(1); }); Promise.resolve().then(() => { console.log(2); }); console.log(3); 复制代码
输出的结果:3,2,1
在尚未接触的Event Loop
以前,看到这个结果的时候,说实话,我是很懵逼的。
OK,到这里,咱们须要先知道两个概念:task
(任务),microtask
(微任务);
这里提一点,网上不少博客说到了一个
macrotask
(宏任务)其实跟这个task
(任务)是一个东西。你能够参考换一下HTML5规范的文档,里面甚至没有macrotask
这个词,“宏”这个概念,只是为了更好区分任务和微任务的关系。
经过仔细阅读文档得知,这两个概念属于对异步任务的分类,不一样的API注册的异步任务会依次进入自身对应的队列中,而后等待Event Loop将它们依次压入执行栈中执行。
task主要包含:主代码
、setTimeout
、setInterval
、setImmediate
、I/O
、UI交互事件
microtask主要包含:Promise
、process.nextTick
、MutaionObserver
这里提一点:Promise的then方法,会将传入的回调函数,加入到
microtask queue
中。
而后接下来,你须要知道Event Loop
的每一个循环的流程:
microtask
(微任务),直到全部微任务清空为止如今带着渲染进程的知识,结合这个流程,来捋一遍上面代码:
task
,因而它进入JS引擎线程中的执行栈开始执行。setTimeout
方法被调用,也进入执行栈,将一个延时的异步事件交给了定时器触发线程去读数,而后它立刻退出执行栈。Promise.resolve()
进入执行栈,返回一个Promise
,退出执行栈。then()
方法进入执行栈,将一个函数加入了microtask queue
(微任务队列),退出执行栈。console.log(3)
进入执行栈,输出3,退出执行栈。task
,退出执行栈。microtask queue
,发现一个()=>{ console.log(2) }
函数,压入执行栈,输出2,退出执行栈。microtask queue
被清空,切到GUI线程,看是有须要变更UI的,第一轮循环完毕。setTimeout
发起的定时器是在定时器触发线程并发进行,读数完毕,回调交给事件触发线程中的task queue
。因此,此时任务队列中有一个待执行的task
。task
压入执行栈执行,输出1,而后退出执行栈。Event Loop
,继续重复上面的流程执行。Event Loop
的不断循环,保证了咱们的JS代码同步和异步代码的有序执行。
如今回答一下上面提出的两个问题:
task
=>microtask
=>GUI
microtask
(微任务)ES6新引入了Promise标准,同时浏览器实现上多了一个microtask微任务概念。在浏览器上,主要有两个微任务API:
Promise.then
mutation observer
第一个你们应该都熟悉,第二个呢,我以前也不知道,是后来再看Vue的nextTick源码中看到的,有兴趣的同窗能够去了解一下这个API。
这里主要说一下微任务和宏任务的不一样点,和相同点。
不一样点:
task
microtask queue
(微任务队列)。task
执行完毕,而后全部的microtask
都要被执行完。相同点:
如今再来把最开始提出的后三个问题回顾一下,应该有一个大体的概念了吧。
其实原本是想写,结合Event Loop来理解Vue的异步批量更新以及nextTcik的,可是后面发现Event Loop这块写的太多了,因而就分开写了。。。
可是,我相信你看完上面的所有内容,在面试的时候,或者碰到异步相关问题的时候,都应该可以应付了。其实这个Event Loop
中还有一些用户交互事件没详细讲到,有兴趣的能够自行研究一下。
Tips:若是有错误或者有歧义的地方,能够在直接指出。
本篇参考的资料:
「硬核JS」一次搞懂JS运行机制(这篇博客中,说到关于浏览器进程和线程的知识,讲解的很是详细,同时对Event Loop也是总结的很是好)
Event Loop的规范和实现(这个主要讲Event Loop,也是很是的通俗易懂,里面的许多案例值得参考)