js Event Loop 运行机制
栏目: JavaScript · 发布时间: 6年前
内容简介:Event Loop,事件环,线程进程。这些概念对初识前端的同学来说可能会一头雾水。而且运行js代码的运行环境除了浏览器还有node。因此不同环境处理Event Loop又变得不同,十分容易混淆。如果你有这样的疑问。下文将给你一个清晰的解释。首先我们简化一下概念,把进程,线程,事件环,这些概念梳理一下。清晰了概念后面用到的时候就会有共鸣。拿出在教科书里的概念:
Event Loop,事件环,线程进程。这些概念对初识前端的同学来说可能会一头雾水。而且运行js代码的运行环境除了浏览器还有node。因此不同环境处理Event Loop又变得不同,十分容易混淆。如果你有这样的疑问。下文将给你一个清晰的解释。
概念梳理
首先我们简化一下概念,把进程,线程,事件环,这些概念梳理一下。清晰了概念后面用到的时候就会有共鸣。
进程和线程基本概念
拿出在教科书里的概念:
1、调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位;
2、并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行;
3、拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源;
4、系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。
进程和线程的关系:
- 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程;
- 资源分配给进程,同一进程的所有线程共享该进程的所有资源;
- 处理机分给线程,即真正在处理机上运行的是线程;
- 线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。线程是指进程内的一个执行单元,也是进程内的可调度实体。
第一次看可能并没什么共鸣。但是带着最基本的想法,一个进程可以有多个线程,线程之间可以相互通信。这两点,就足够你理解后续事件环的知识。
浏览器中的进程和线程和Event Loop
浏览器的进程
- 从打开浏览器开始,打开浏览器,我们首先看到的是,用户界面,这里有搜索框,显示区,还有收藏夹等等。这些会分配一个进程。
- 我们看到浏览器自己会实现一些本地存储,cookie等,这些操作也需要分配一个进程。
3. 打开一个浏览器的tab页,他如果想要运行就需要系统分配给他cpu和内存资源,因此他需要分配一个进程。对应上面的概念“进程是拥有资源的基本单位”。因此每打开一个tab就对应一个新的进程。从资源管理器中进程可以看到,chrome占用多个进程。(有些系统会对进程进行整合,win10下可能看到的效果不同)
眼见为实,我们才能说浏览器是多线程的。那我们用可视化的角度看一下浏览器的这个进程和线程结构
从图中看黄色的圆角框里包裹的都是进程。蓝色的直角框里包裹的都是浏览器渲染引擎(浏览器内核)所包含的线程。对应上面的概念“一个进程可以有多个线程,但至少有一个线程”。前三个进程刚刚在1-3里都说过了。 介绍了刚刚那么多我们前端的操作其实都是在3中浏览器渲染引擎中处理。真正干活的就是线程。对应上面的概念“处理机分给线程,即真正在处理机上运行的是线程”。
浏览器内核的线程
接下来看一下浏览器引擎(进程)中包含哪些线程
- UI渲染线程 负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等。 当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行
注意
:UI渲染线程与JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起(相当于被冻结了),UI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。
- js引擎线程(JS解析线程) 也称为JS内核,负责处理Javascript脚本程序。(例如V8引擎) JS引擎线程负责解析Javascript脚本,运行代码。 JS引擎一直等待着任务队列中任务的到来,然后加以处理,一个Tab页(renderer进程)中无论什么时候都__只有一个JS线程在运行JS程序__
同样注意
:UI渲染线程与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渲染引擎的Event Loop
以上线程,每个拿出来都可以详细的说上一篇。Event Loop涉及到的JS引擎的一些运行机制的分析。我们可以将这些线程理解为,
- 一个主进程就是js引擎,其他均为辅助的线程。
- 主进程存在一个执行栈,事件触发线程维护一个消息队列
- 同步任务在执行栈中执行,异步任务在满足条件后加入到消息队列中,等待执行。
- 先执行栈中的任务,执行完毕后,检查队列是否为空,不为空,将队列中的任务压入执行栈中执行。直到栈和队列均为空。 js渲染引擎的Event Loop如下图
这时候拿出几道题看一下会更清晰 题目1:
setTimeout(function(){ console.log(0) },500) setTimeout(function(){ console.log(1) },1000) setTimeout(function(){ console.log(2) },2000) for(;;){ } 复制代码
上面这段代码用于不会有输出,同步代码死循环阻塞了执行栈。虽然定时后回调加入执行队列,但是异永远不会执行。
题目二:
setTimeout(function(){ console.log('setTimeout1'); Promise.resolve().then(()=>{ console.log('then1'); }); },0) Promise.resolve().then(()=>{ console.log('then2'); Promise.resolve().then(()=>{ console.log('then3'); }) setTimeout(function(){ console.log('setTimeout2'); },0) }) 复制代码
答案:then2 then3 setTimeout1 then1 setTimeout2
首先在题目中出现了es6的promise,他的出现让原来我们理解的__事件环产生了一些不同__。 为什么呢?因为Promise里有了一个一个新的概念:microtask 此时JS中分为__两种任务类型__:macrotask和microtask,在ECMAScript中,microtask称为jobs,macrotask可称为task
微任务和宏任务
首先说明,是以__浏览器为处理环境__下的执行逻辑 浏览器环境下的微任务和宏任务有哪些 宏任务:setTimeout setImmediate MessageChannel 微任务:Promise.then MutationObserver
记住两点:
- 微任务在宏任务之前的执行,先执行 执行栈中的内容 执行后 清空微任务
- 每次取一个宏任务 就去清空微任务,之后再去取宏任务
然后题目入手分析宏任务和微任务的执行
- 微任务宏任务同时出现,先将微任务放入执行队列,栈为空则先执行then2。setTimeout1放入宏任务执行队列中
- then2之后执行后,接下来存在微任务then3。将then3放入微任务队列中。
- 接下来setTimeout2加入到宏任务队列中。
- 此时执行栈为空,执行then3。
- 微任务全部执行完毕后,执行宏任务setTimeout1,执行发现微任务then1,放置到微任务队列中。
- setTimeout1宏任务执行完,再次清空微任务队列,执行then1
- 微任务全部执行完毕后,执行宏任务setTimeout2。程序结束。
node运行环境中的进程和线程
Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。他的目标就是解析js代码,让他能运行起来。
node js 是单线程的
和浏览器环境下类似,他有一个解析js的主线程,其他线程作为辅助,但是因为不涉及操作dom,ui线程就不存在了。(各个线程的概念参考浏览器环境下的线程)
单线程在浏览器运行环境中的弊端体现在阻塞页面执行。
那么node作为后端服务,单线程有什么利弊?
优点:
- 避免频繁创建、切换进程的开销,使执行速度更加迅速。
- 资源占用小
- 线程安全,不用担心同一变量同时被多个线程进行读写而造成的程序崩溃。 缺点:
- 不适合大量的计算和压缩等cpu密集型的操作,会造成阻塞。
node下Event Loop
事件环的整体还是不变的,执行栈,消息队列,api。不同的是,node下的消息队列有所不同
分析一下node下的消息队列
- 为微任务,定时器,io,setImmidiate分别分配消息队列
- 先检查定时器队列,如果有内容,则全部清空
- 从时间队列切换到io队列的过程中,检查微任务,如果有则情况微任务。
- io队列执行完成,如果有check队列的内容,则执行。否则继续检查定时器队列。
- 完成闭环
从一个题目入手感受一下node环境和浏览器环境下的不同
setTimeout(() => { console.log('timeout1'); Promise.resolve().then(() => { console.log('promise'); }); }, 0) setTimeout(() => { console.log('timeout2'); }, 0) 复制代码
浏览器下的结果:timeout1 promise timeout2
node下的结果:timout1 timeout2 promise
微任务和宏任务
node环境下的微任务和宏任务有哪些 宏任务:setTimeout setImmediate 微任务:Promise.then process.nextTick
题目三可以很好的分析node环境下的任务执行 node环境下运行流程
- 首先遇到两个宏任务,均放入到时间队列里。
- 执行时间队列里第一个宏任务时timeout1,遇到微任务promise,放到微任务队列中
- 此时时间队列还未清空,继续执行完成所有时间队列里的任务。执行timout2
- 在切换io队列时检查微任务,有则执行清空微任务。执行promise。
浏览器环境下运行流程 - 首先遇到两个宏任务,均放入到宏任务队列里。
- 执行时间队列里第一个宏任务时timeout1,遇到微任务promise,放到微任务队列中
- timout1执行完成检查微任务,有内容则执行清空,执行promise。
- 清空微任务后再执行宏任务。执行timeout2
注意
:同样是微任务,process.nextTick,优于promise.then先执行
Promise.resolve().then(() => { console.log('then') }) process.nextTick(() => { console.log('nextTick') }); //nextTick then 复制代码
注意
:同样是宏任务,setTimeout和setImediate执行的先后顺序是不确定的,依赖于执行栈执行的速度。
setImmediate(function () { console.log('setImmediate') }); setTimeout(function () { console.log('setTimeout') }, 0); // ->4 复制代码
但是在如下场景下是有固定输出的
let fs = require('fs'); fs.readFile('./gitignore', function () { // io的下一个事件队列是check阶段 setImmediate(function () { console.log('setImmediate') }); setTimeout(function () { console.log('setTimeout') }, 0); // ->4 }) 复制代码
给个提示,读文件是io操作,io执行之后首先要check,check之后或没有check内容再去检查定时队列。 那么结果就留给大家自行分析了。
以上所述就是小编给大家介绍的《js Event Loop 运行机制》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
微机原理与接口技术
李文英、李勤、刘星、宋蕴新 / 清华大学出版社 / 2001-9 / 26.00元
《高等院校计算机应用技术规划教材•应用型教材系列•微机原理与接口技术》是“高职高专计算机系列教材”之一。全书包括微机原理、汇编语言、接口技术三部分内容。微机原理部分讲述了80x86的内部结构及工作原理、半导体存储器及其系统、微型机总线结构等。汇编语言部分讲述了指令系统、编程技巧。接口技术部分讲述了中断系统、中断控制器、并行接口、串行接口、DMA控制器、定时器,以及A/D、D/A转换器等常用芯片的硬......一起来看看 《微机原理与接口技术》 这本书的介绍吧!