浏览器进程和事件循环eventloop机制(三)

一、浏览器渲染进程(浏览器内核),Event Loop机制用到的地方

1、再来看一下浏览器渲染进程中包含的线程

2、几个线程之间的关系

01、GUI渲染线程与JS引擎线程互斥

       第一,由于JavaScript是可操纵DOM元素的,如果在修改这些元素属性同时渲染界面(即JS线程和UI线程同时运行),那么渲染线程前后获得的元素数据就可能不一致了。

       第二,由于JavaScript没有锁的概念,如果同时操作DOM元素,会发生冲突,产生问题

        因此为了防止渲染出现不可预期的结果,浏览器设置GUI渲染线程与JS引擎为互斥的关系,当JS引擎执行时,GUI线程会被挂起,GUI的更新则会被保存在一个队列中等到JS引擎线程空闲时立即被执行。 

02、JS阻塞页面加载(比如巨量计算)

        从上述的互斥关系,可以推导出,JS如果执行时间过长就会阻塞页面,比如内存溢出,debugger操作,alert操作等会造成页面卡死现象。

        再譬如,假设JS引擎正在进行巨量的计算,此时就算GUI有更新,也会被保存到队列中,等待JS引擎空闲后执行。
        然后,由于巨量计算,所以JS引擎很可能很久很久后才能空闲,自然会感觉到巨卡无比。所以,要尽量避免JS执行时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。

03、WebWorker的应运而生(巨量计算)

HTML5中支持了Web Worker

MDN的官方解释是:

Web Worker为Web内容在后台线程中运行脚本提供了一种简单的方法。线程可以执行任务而不干扰用户界面

一个worker是使用一个构造函数创建的一个对象(e.g. Worker()) 运行一个命名的JavaScript文件 

这个文件包含将在工作线程中运行的代码; workers 运行在另一个全局上下文中,不同于当前的window

因此,使用 window快捷方式获取当前全局的范围 (而不是self) 在一个 Worker 内将返回错误

这样理解下:

  • 创建Worker时,JS引擎向浏览器申请开一个子线程(子线程是浏览器开的,完全受主线程控制,而且不能操作DOM)
  • JS引擎线程与worker线程间通过特定的方式通信(postMessage API,需要通过序列化对象来与线程交互特定的数据)

所以,如果有非常耗时的工作,请单独开一个Worker线程,这样里面不管如何翻天覆地都不会影响JS引擎主线程,
只待计算出结果后,将结果通信给主线程即可,perfect!

而且注意下,JS引擎是单线程的,这一点的本质仍然未改变,Worker可以理解是浏览器给JS引擎开的外挂,专门用来解决那些大量计算问题。

其它,关于Worker的详解就不是本文的范畴了,因此不再赘述。

4、WebWorker线程与SharedWorker进程区别

  • WebWorker只属于某个页面,不会和其他页面的Render进程(浏览器内核进程)共享
    • 所以Chrome在Render进程中(每一个Tab页就是一个render进程)创建一个新的线程来运行Worker中的JavaScript程序。
  • SharedWorker是浏览器所有页面共享的,不能采用与Worker同样的方式实现,因为它不隶属于某个Render进程,可以为多个Render进程共享使用
    • 所以Chrome浏览器为SharedWorker单独创建一个进程来运行JavaScript程序,在浏览器中每个相同的JavaScript只存在一个SharedWorker进程,不管它被创建多少次。

看到这里,应该就很容易明白了,本质上就是进程和线程的区别。SharedWorker由独立的进程管理,WebWorker只是属于render进程下的一个线程;

二、JS中Event Loop的运行机制

1、Event Loop简易图

1、先了解几个概念

JS函数执行栈:栈是先进后出,压栈,类似于乐事薯片,先进后面;

JS回调队列:先进先出;

JS任务:  分为同步任务和异步任务,同步任务在主线程上形成一个执行栈;

宏任务队列(macrotask queue):所有同步任务,<script></script>标签,I/O操作,setTimeout,setInterval,requestAnimationFrame,事件监听回调函数(ajax)等;

微任务(v)队列(microtask queue):Promise的then方法(包括catch和finally),async/await中的代码,process.nextTick(Node.js环境);

声明:

        javaScript是一门单线程语言(目前没有锁的概念),在一个<script></script>标签中,也是依次同步任务从上往下一次执行,异步任务先交给分线程管理;

        所有任务都是在主线程上执行( javaScript是一门单线程语言),一根管道,从头到尾;

2、理解图中EventLoop(微任务和宏任务没有区分)流程

1、主线程读取<script></script>中代码,先进行预编译进行变量提升,再从上向下执行,形成相应的堆和执行栈;

2、当主线程执行过程中遇到异步任务时,将其委托给对应的异步线程(如:定时器管理模块即定时器触发线程);

3、当异步任务线程管理的异步任务有了结果后,便把该结果放入任务队列(回调队列)中;

4、当主线程执行完毕之后(js引擎线程处在空闲中);

     系统会看一下微任务队列(后面会说),有没有微任务需要执行;

     没有微任务,则去宏任务队列中把一个任务放入执行栈中,js执行该宏任务;

     系统再去看一下微任务队列有没有微任务,再去宏任务队列中读取一个宏任务放入执行栈中;

5、重复执行以上步骤,形成事件循环;

6、总是要等到栈中任务执行完毕,系统才会读取队列中的任务;

三、JS中Event Loop进阶(区分宏任务和微任务)

        1、JS引擎先执行同步的任务(宏任务),从上往下执行;

        2、执行完毕,再去看有没有微任务(微任务维护在JS引擎线程的任务队列中);

        3、一次性执行完所以得微任务后,再去(事件触发线程的任务队列中)获取有宏任务,有的话,执行一个宏任务;

        4、再去JS引擎线程的任务队列中,看有没有微任务,有的话,一次性执行完所以得微任务后,再去(事件触发线程的任务队列中)获取有宏任务,有的话,执行一个宏任务;

       5、执行顺序一般是同步宏任务->微任务->宏任务,这里的宏和微并非单纯的,可能一个宏任务里面包含了N个微任务,这个微任务里面又包含宏任务和下一个微任务,循环嵌套;

       6、微任务队列里面有多少,一次性执行完;

       7、宏任务队列是一次去一个,执行完,在去看微任务队列,再去看宏任务队列;    

四、浏览器线程理解,microtask与macrotask

       ES6新引入了Promise标准,同时浏览器实现上多了一个microtask微任务概念。在ECMAScript中,microtask称为jobs,macrotask可称为task。

1、macrotask宏任务(tasks),也就是上面说到的任务队列的任务。执行栈上的每个任务都属于宏任务,主线程执行完执行栈的宏任务,从任务队列取新的任务。

        每一个宏任务执行时不会中断,会一次性执行完,为了及时渲染数据,主线程执行完一个宏任务之后,会执行一次渲染。

        浏览器为了能够使得JS内部task与DOM任务能够有序的执行,会在一个task执行结束后,在下一个 task 执行开始前,对页面进行重新渲染 (task->渲染->task->...

流程:宏任务(task)--》渲染 --》宏任务(task) --》渲染  .....

2、microtask微任务(jobs),可以看成是VIP任务,会在当前主线程task任务执行后,渲染线程渲染之前,执行完当前积累所有的微任务

宏任务(task)--》所有微任务(jobs) --》渲染 --》宏任务(task) --》所有微任务(jobs) --》渲染  .....

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值