关于JavaScript的执行机制

2021-03-03 13:28

阅读:475

标签:设置   tin   多次   pos   遇到   超过   syn   回调   end   

浏览器线程
一般来讲,一个程序一个进程,例如:低版本IE。Chrome为提高速度和保证访问一个有问题的页面,页面奔溃不影响其他的页面,采用沙箱技术。是一个网页一个进程,这也是Chrome吃内存的原因所在)。
浏览器包含有以下线程:
GUI渲染线程
? 负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等。
? 当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行。
? GUI渲染线程与JavaScript引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起(被冻结),GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。
JavaScript引擎线程(JavaScript内核)
? 负责处理Javascript脚本程序(例如V8引擎)。
? JavaScript引擎线程负责解析Javascript脚本,执行代码。
? JavaScript引擎一直等待着任务队列中任务的到来,然后加以处理,一个标签页(进程)中无论什么时候都只有一个JavaScript线程在执行JavaScript代码。
GUI渲染线程与JS引擎线程是互斥的,因为JavaScript脚本是可操纵DOM元素,在修改这些元素属性同时渲染界面,那么渲染线程前后获得的元素数据就可能不一致了。如果JavaScript执行的时间过长,会造成页面的渲染不连贯,导致页面渲染加载阻塞。
事件触发线程
? 归属于浏览器而不是JavaScript引擎,用来控制事件循环(浏览器另开线程协助JavaScript引擎)。
? 当JavaScript引擎执行代码块如鼠标点击、键盘事件等,会将对应任务添加到事件线程中。
? 当对应的事件符合触发条件被触发时,事件触发线程会把事件的回调函数添加到任务队列中,等待JS引擎空闲后执行。
定时触发器线程
? setInterval与setTimeout所在线程
? 浏览器定时计数器并不是由JavaScript引擎计数的,因此通过单独线程来计时并触发定时。计时完毕后,定时触发器线程将回调函数添加到任务队列中,等待JS引擎空闲后执行
? 注意,W3C规定要求setTimeout中低于4ms的时间间隔算为4ms。
异步http请求线程
? 在XMLHttpRequest在连接后是通过浏览器新开一个线程请求
? 将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中,再由JavaScript引擎执行。
JavaScript 引擎最大特点就是:单线程,同一个时间只能做一件事。
JavaScript引擎自上而下依次执行代码,如果遇到一段代码特别花费时间,就会阻塞后面代码的执行,造成浏览器「假死」状态。
因此,将任务分为:同步任务和异步任务。用一个执行机制来合理运行相关代码,这个机制就是:事件循环。
事件循环 Event Loop
技术图片
图表述:
? 同步任务和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table。
? 当指定的事情完成时,Event Table会将这个事件注册的回调函数移入Event Queue。
? 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。
? 上述过程会不断重复,就是Event Loop(事件循环)。
同步任务
在主线程上排队执行的任务,只有前一个任务执行完毕,后一个任务才可以执行。
异步任务
不直接进入JavaScript主线程,而进入eventTable。
异步任务例子:
setTimeout(() => console.log(‘我是异步任务的回调函数代码‘), 5000)
由定时器触发线程去计算这个5000ms,5000ms后,定时器触发线程将定时器中的回调函数
() => console.log(‘我是异步任务的回调函数代码‘)
放在任务队列中(事件队列/eventQueue),当JavaScript主线程执行完所有的同步任务后空闲时,主线程去任务队列读取回调函数,拿到JavaScript主线程中进行执行。
那是怎么知道主线程执行栈为空呢?
JavaScript引擎存在monitoring process进程,会持续不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里读取是否有等待被调用的函数。
宏任务 macrotask
定义:每次执行栈执行的代码就是一个宏任务(包括同步任务、每次从事件队列中获取一个事件回调并放到执行栈中执行)。
浏览器为了能够使得JS内部macrotask与DOM任务能够有序的执行,会在一个task执行结束后,在下一个 macrotask 执行开始前,对页面进行重新渲染 (macrotask->渲染->macrotask->...) 鼠标点击会触发一个事件回调,需要执行一个宏任务,然后解析HTMl。
宏任务包含:
? 一片整体代码/script标签内整体的代码
? setTimeout setInterval
? UI交互事件
? postMessage
? MessageChannel
? I/O 交互
? setImmediate(Node.js 环境)
setImmediate与setTimeout的区别
在官方文档中的定义,setImmediate为一次Event Loop执行完毕后调用。
setTimeout则是通过计算一个延迟时间后进行执行。
但是同时还提到了如果在主进程中直接执行这两个操作,很难保证哪个会先触发。
因为如果主进程中先注册了两个任务,然后执行的代码耗时超过XXs,而这时定时器已经处于可执行回调的状态了。
所以会先执行定时器,而执行完定时器以后才是结束了一次Event Loop,这时才会执行setImmediate。
setTimeout(() => console.log(‘setTimeout‘))
setImmediate(() => console.log(‘setImmediate‘))
// node环境里执行多次,结果会不同。
微任务 microtask
需要在当前 macrotask 执行结束后立即执行的任务,比如对一系列动作做出反馈,或者是需要异步的执行任务而又不需要分配一个新的task,这样便可以减小一点性能的开销。
只要执行栈中没有其他的js代码正在执行且每个宏任务执行完,微任务队列会立即执行。如果在微任务执行期间微任务队列加入了新的微任务,会将新的微任务加入队列尾部,之后也会被执行。
微任务包含:
? Promise.then/catch/finally
? async/await函数
? Object.observe
? MutaionObserver
? process.nextTick(Node.js 环境)
async/await本质上是基于Promise的一些封装,而Promise是属于微任务的一种。所以在使用await关键字与Promise.then效果类似。
async函数在await之前的代码都是同步执行的,await之后的所有代码都是在Promise.then中的回调。
setTimeout(() => console.log(4))
async function fn() {
console.log(1)
await Promise.resolve()
console.log(3)
}
fn()
console.log(2)
// 1 2 3 4
process.nextTick
含义:定义出一个动作,并且让这个动作在下一个事件轮询的时间点上执行。
它是在本轮循环执行的,而且是所有异步任务里面最快执行的。
可以用settimeout模拟效果,但是内部机制不同,不做深入研究。
宏任务和微任务之间的关系图:
技术图片
在每一次的事件循环过程中关键步骤如下:
? 执行一个宏任务(栈中没有就从事件队列中获取宏任务)
? 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
? 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
? 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
? 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)
宏任务,微任务总结:
? 宏任务按顺序执行,且浏览器在每个宏任务执行之间渲染页面。
? 微任务也按顺序执行,且在以下场景会立即执行所有微任务。
? 每个回调之后且js执行栈中为空。
? 每个宏任务结束后。

转至:https://www.yuque.com/liaoing/blog/wbx8cu

关于JavaScript的执行机制

标签:设置   tin   多次   pos   遇到   超过   syn   回调   end   

原文地址:https://www.cnblogs.com/bomdeyada/p/14388929.html


评论


亲,登录后才可以留言!