美文网首页
浏览器的进程、线程、Web Worker及Event Loop

浏览器的进程、线程、Web Worker及Event Loop

作者: 李霖弢 | 来源:发表于2019-12-17 11:23 被阅读0次

进程和线程

系统会为进程分配cpu和内存
每个进程至少包含一个线程
不同进程之间也可以通信,不过代价较大

浏览器是多进程的

通过Chrome的更多工具 -> 任务管理器 可以查看进程信息
通常每个网页的渲染进程(Renderer)和每种第三方插件占用一个进程,所有网页公用一个Browser主进程(前进、后退、下载等)和GPU绘图进程

其中CSS由下载线程进行下载,不会阻塞DOM树解析,但会阻塞render树渲染


网页的渲染进程(Render)
  1. GUI线程
    负责绘制、回流、重绘。注意GUI线程不会和JS线程同时执行,执行JS时GUI会被暂时挂起
  2. JS引擎线程
    一个Tab页(renderer进程)中无论什么时候都只有一个JS线程在运行JS程序
  3. 事件触发线程
    来自浏览器内核的事件及JS引擎中的异步任务会被添加进事件触发线程,当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列的队尾,等待JS引擎线程 的处理
  4. 定时触发器线程
  5. 异步http请求线程
    在XMLHttpRequest在连接后是通过浏览器新开一个线程请求
    将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中。再由JavaScript引擎执行。
Web Worker 线程 (IE11以上支持)

JS引擎线程向浏览器申请开一个子线程(仅能通过.postMessage()与JS线程交互,而且不能操作DOM)用于处理复杂JS,防止阻塞页面。
逻辑完成后应在主线程中调用.terminate()或Worker中调用close()方法关闭Worker以释放资源,否则会一直占用
postMessage传递对象时仅传值不传址(先将通信内容串行化,然后把串行化后的字符串发给 Worker,再还原。)

  • 主线程中
    • 引用的Web Worker的脚本文件必须和主线程同源
    • 通过.postMessage()发送消息,通过.onmessage()接收消息
    • 通过.onerror(function (event) {})或者.addEventListener('error', function (event) {})可监听Worker中的错误
var worker = new Worker('test.js');
worker.postMessage('Hello World');
worker.postMessage({ method: 'echo', args: ['Work'] });

worker.onmessage = function (event) {
    console.log('Received message ' + event.data);
    worker.terminate();
}
  • Worker线程中
    • Worker 线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的 DOM 对象,也无法使用documentwindowparent等(没有alertconfirm方法),仅有navigator对象和location对象。
    • 全局对象可用selfthis表示,也可直接省略(同主线程中的window)
    • 通过addEventListener('message',function(e){})onmessage()监听主线程推送的消息,或通过postMessage()推送消息
    • 通过importScripts()加载其他js脚本
    • Worker 线程无法读取本地文件,即不能打开本机的文件系统(file://),它所加载的脚本,必须来自网络。
// 写法零
self.addEventListener('message', function (e) {
  self.postMessage('You said: ' + e.data);
}, false);
// 写法一
this.addEventListener('message', function (e) {
  this.postMessage('You said: ' + e.data);
}, false);
// 写法二
addEventListener('message', function (e) {
  postMessage('You said: ' + e.data);
}, false);
复合图层(硬件加速)

dom改变会触发当前复合层的回流重绘,默认情况下所有dom处于同一复合层。
复合图层会占用额外资源。
通过硬件加速可以创建新的复合层,以提高重绘效率。

  • 硬件加速通常有如下方法:

    1. translate3d、translateZ
    2. opacity属性/过渡动画(需要动画执行的过程中才会创建合成层,动画没有开始或结束后元素还会回到之前的状态)
    3. <video><iframe><canvas><webgl>等元素
    4. 其它,譬如以前的flash插件
  • 硬件加速时请使用index
    webkit CSS3中,如果这个元素添加了硬件加速,并且index层级比较低,
    那么在这个元素的后面其它元素(层级比这个元素高的,或者相同的,并且releative或absolute属性相同的),
    会默认变为复合层渲染,如果处理不当会极大的影响性能

Event Loop

同步任务都在JS线程上执行,形成一个执行栈。
JS线程之外,事件触发线程管理着一个任务队列,异步任务完成后会将其回调加入任务队列。
一旦执行栈中的所有同步任务执行完毕(此时JS引擎空闲),系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行。
定时器线程会在倒计时完成时将任务加入任务队列,但任务的执行依然得等到JS线程空闲,因此JS通过计时器执行任务是不准确的

macrotask与microtask

task->process.nextTick->其他jobs->渲染->task->...

  • macrotask(又称之为宏任务或task)
    包括每次执行的主代码块setTimeoutsetIntervalpostMessagesetImmediate
    每一个task会从头到尾将这个任务执行完毕,不会执行其它
    浏览器为了能够使得JS内部task与DOM任务能够有序的执行,会在一个task执行结束后,在下一个 task 执行开始前,对页面进行重新渲染
    队列由事件触发线程维护(会进入任务队列)

  • microtask(又称为微任务或jobs)
    process.nextTick(高于其他微任务)Promise.then catch finally(注意我不是说 Promise)、MutationObserver、被 await 阻塞的语句等。
    在某一个macrotask执行完后,就会将在它执行期间产生的所有microtask都执行完毕(在渲染前,并在渲染阶段触发requestAnimationFrame
    在处理microtask期间,如果有新添加的microtasks,也会被添加到当前微任务队列的末尾

    队列由JS引擎线程维护(不进入任务队列,有自己专门的微任务队列)

    • Promise的polyfill,一般都是通过setTimeout模拟的,所以是macrotask形式
    setTimeout(function(){
        console.log(1)
    },0);
    new Promise(function(resolve){
        console.log(2)
        for( var i=100000 ; i>0 ; i-- ){
            i==1 && resolve()
        }
        console.log(3)
    }).then(function(){
        console.log(4)
    });
    console.log(5);

// 2 3 5 4 1
setTimeout和setInterval
  • 把浏览器最小化显示等操作时,setInterval的回调函数依然会进入队列,等浏览器窗口再次打开时,一瞬间全部执行
    部分浏览器会对setInterval进行优化,如果当前事件队列中有setInterval的回调,不会重复添加。
  • 一般认为的最佳方案是:用setTimeout模拟setInterval,或者特殊场合直接用requestAnimationFrame

相关文章

网友评论

      本文标题:浏览器的进程、线程、Web Worker及Event Loop

      本文链接:https://www.haomeiwen.com/subject/ctidnctx.html