不少新手是区分不清线程和进程的,没有关系。这很正常。先看看下面这个形象的比喻:html
进程是一个工厂,工厂有它的独立资源-工厂之间相互独立-线程是工厂中的工人,多个工人协做完成任务-工厂内有一个或多个工人-工人之间共享空间
若是是windows电脑中,能够打开任务管理器,能够看到有一个后台进程列表。对,那里就是查看进程的地方,并且能够看到每一个进程的内存资源信息以及cpu占有率。vue
因此,应该更容易理解了:进程是cpu资源分配的最小单位(系统会给它分配内存)java
最后,再用较为官方的术语描述一遍:node
提示:react
理解了进程与线程了区别后,接下来对浏览器进行必定程度上的认识:(先看下简化理解)web
关于以上几点的验证,请再第一张图:ajax
图中打开了Chrome浏览器的多个标签页,而后能够在Chrome的任务管理器中看到有多个进程(分别是每个Tab页面有一个独立的进程,以及一个主进程)。segmentfault
感兴趣的能够自行尝试下,若是再多打开一个Tab页,进程正常会+1以上(不过,某些版本的ie倒是单进程的)
注意:在这里浏览器应该也有本身的优化机制,有时候打开多个tab页后,能够在Chrome任务管理器中看到,有些进程被合并了(因此每个Tab标签对应一个进程并不必定是绝对的)windows
JavaScript语言的一大特色就是单线程,也就是说,同一个时间只能作一件事。那么,为何JavaScript不能有多个线程呢?这样能提升效率啊。api
JavaScript的单线程,与它的用途有关。做为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操做DOM。这决定了它只能是单线程,不然会带来很复杂的同步问题。好比,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另外一个线程删除了这个节点,这时浏览器应该以哪一个线程为准?
因此,为了不复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,未来也不会改变。
为了利用多核CPU的计算能力,HTML5提出Web Worker标准,容许JavaScript脚本建立多个线程,可是子线程彻底受主线程控制,且不得操做DOM。因此,这个新标准并无改变JavaScript单线程的本质。
单线程就意味着,全部任务须要排队,前一个任务结束,才会执行后一个任务。若是前一个任务耗时很长,后一个任务就不得不一直等着。
js引擎执行异步代码而不用等待,是因有为有 消息队列和事件循环。
消息队列:消息队列是一个先进先出的队列,它里面存放着各类消息。
事件循环:事件循环是指主线程重复从消息队列中取消息、执行的过程。
实际上,主线程只会作一件事情,就是从消息队列里面取消息、执行消息,再取消息、再执行。当消息队列为空时,就会等待直到消息队列变成非空。并且主线程只有在将当前的消息执行完成后,才会去取下一个消息。这种机制就叫作事件循环机制,取一个消息并执行的过程叫作一次循环。
事件循环用代码表示大概是这样的:
while(true) { var message = queue.get(); execute(message); }
那么,消息队列中放的消息具体是什么东西?消息的具体结构固然跟具体的实现有关,可是为了简单起见,咱们能够认为:
消息就是注册异步任务时添加的回调函数。
再次以异步AJAX为例,假设存在以下的代码:
$.ajax('http://segmentfault.com', function(resp) { console.log('我是响应:', resp); }); // 其余代码 ... ... ...
主线程在发起AJAX请求后,会继续执行其余代码。AJAX线程负责请求segmentfault.com,拿到响应后,它会把响应封装成一个JavaScript对象,而后构造一条消息:
// 消息队列中的消息就长这个样子 var message = function () { callbackFn(response); }
其中的callbackFn就是前面代码中获得成功响应时的回调函数。
主线程在执行完当前循环中的全部代码后,就会到消息队列取出这条消息(也就是message函数),并执行它。到此为止,就完成了工做线程对主线程的通知,回调函数也就获得了执行。若是一开始主线程就没有提供回调函数,AJAX线程在收到HTTP响应后,也就不必通知主线程,从而也不必往消息队列放消息。
用图表示这个过程就是:
从上文中咱们也能够获得这样一个明显的结论,就是:
异步过程的回调函数,必定不在当前这一轮事件循环中执行。
一张图展现JavaScript中的事件循环:
一次事件循环:先运行macroTask队列中的一个,而后运行microTask队列中的全部任务。接着开始下一次循环(只是针对macroTask和microTask,一次完整的事件循环会比这个复杂的多)。
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, setImmediate, requestAnimationFrame, I/O, UI rendering(能够看到,事件队列中的每个事件都是一个macrotask)
microTask: process.nextTick, Promise, Object.observe, MutationObserver
补充:在node环境下,process.nextTick的优先级高于Promise,也就是能够简单理解为:在宏任务结束后会先执行微任务队列中的nextTickQueue部分,而后才会执行微任务中的Promise部分。
另外,setImmediate则是规定:在下一次Event Loop(宏任务)时触发(因此它是属于优先级较高的宏任务),(Node.js文档中称,setImmediate指定的回调函数,老是排在setTimeout前面),因此setImmediate若是嵌套的话,是须要通过多个Loop才能完成的,而不会像process.nextTick同样没完没了。
实践:上代码
咱们以setTimeout、process.nextTick、promise为例直观感觉下两种任务队列的运行方式。
console.log('main1'); process.nextTick(function() { console.log('process.nextTick1'); }); setTimeout(function() { console.log('setTimeout'); process.nextTick(function() { console.log('process.nextTick2'); }); }, 0); new Promise(function(resolve, reject) { console.log('promise'); resolve(); }).then(function() { console.log('promise then'); }); console.log('main2');
别着急看答案,先以上面的理论本身想一想,运行结果会是啥?
最终结果是这样的:
main1 promise main2 process.nextTick1 promise then setTimeout process.nextTick2
process.nextTick 和 promise then在 setTimeout 前面输出,已经证实了macroTask和microTask的执行顺序。可是有一点必需要指出的是。上面的图容易给人一个错觉,就是主进程的代码执行以后,会先调用macroTask,再调用microTask,这样在第一个循环里必定是macroTask在前,microTask在后。
可是最终的实践证实:在第一个循环里,process.nextTick1和promise then这两个microTask是在setTimeout这个macroTask里以前输出的,这是为何呢?
由于主进程的代码也属于macroTask(这一点我比较疑惑的是主进程都是一些同步代码,而macroTask和microTask包含的都是一些异步任务,为啥主进程的代码会被划分为macroTask,不过从实践来看确实是这样,并且也有理论支撑:【翻译】Promises/A+规范)。
主进程这个macroTask(也就是main一、promise和main2)执行完了,天然会去执行process.nextTick1和promise then这两个microTask。这是第一个循环。以后的setTimeout和process.nextTick2属于第二个循环
别看上面那段代码好像特别绕,把原理弄清楚了,都同样 ~
requestAnimationFrame、Object.observe(已废弃) 和 MutationObserver这三个任务的运行机制你们能够从上面看到,不一样的只是具体用法不一样。重点说下UI rendering。在HTML规范:event-loop-processing-model里叙述了一次事件循环的处理过程,在处理了macroTask和microTask以后,会进行一次Update the rendering,其中细节比较多,总的来讲会进行一次UI的从新渲染。
这里就直接引用一张图片来协助理解:(参考自Philip Roberts的演讲《Help, I’m stuck in an event-loop》)
上图大体描述就是:
看到这里,应该对JS的运行机制有必定的理解了吧。
参考:
我不是大神,也不是什么牛人,写这个号的目的是为了记录我自学 web全栈 的笔记。
对 全栈修炼 有兴趣的朋友能够扫下方二维码关注个人公众号
我会不按期更新有价值的内容,长期运营。
关注公众号并回复 福利 可领取免费学习资料,福利详情请猛戳: Python、Java、Linux、Go、node、vue、react、javaScript