浏览器组成和各引擎工作原理

45次阅读

共计 4672 个字符,预计需要花费 12 分钟才能阅读完成。

1. 浏览器的主要构成部分

  • 1. 用户界面
  • 2. 浏览器引擎(负责窗口管理、Tab 进程管理等)
  • 3. 渲染引擎(有叫内核,负责 HTML 解析、页面渲染)
  • 4.JS 引擎(JS 解释器,如 Chrome 和 Nodejs 采用的 V8)

这里面最核心的就是渲染引擎和 JS 引擎,后面会详细介绍这两个引擎的相关内容。

常见浏览器的渲染引擎和 JS 引擎如下:

浏览器 渲染引擎 JS引擎
IE Trident Chakra
Edge EdgeHTML Chakra
Firefox Gecko SpiderMonkey
Chrome Webkit -> Blink V8(著名的)
Safri Webkit Javascriptcore
Opera Presto->Blink Carakan

注:新版本的 Chrome 采用的渲染引擎是 Blink,Blink 是由谷歌团队从 Webkit 衍生开发出来的引擎,主要有应用到 Chrome 和 Opera 浏览器。

2. 从进程和线程的角度来理解浏览器工作

1)进程和线程

  • 进程是 cpu 资源分配的最小单位(是能拥有资源和独立运行的最小单位)
  • 线程是 cpu 调度的最小单位(线程是建立在进程的基础上的一个程序运行单位,一个进程中可以有多个线程)

进程可以类比为工厂,线程就是工厂里面的工人,一个工厂可以包含一个或者多个工人,工人之间可以相互协作,并且共享工作空间

2)浏览器的多进程架构

现代的浏览器采用的都是多进程架构,主要包含以下三种进程:

1.Browser 进程

浏览器的主线程,主要负责浏览器的页面管理、书签、前进后退、资源下载管理等,整个浏览器应用程序只有一个,对应上述浏览器组成中的浏览器引擎。

2. 渲染进程

内核进程、负责页面渲染、JS 执行,对应的是上述的渲染引擎和 JS 引擎,一个浏览器可以包含多个渲染进程,每个 Tab 窗口页对应一个渲染进程

3.GPU 进程

负责 GPU 渲染,整个浏览器应用程序只有一个

4. 插件进程

浏览器安装的插件(扩展程序),每个插件会创建一个进程

当打开上面两个 Tab 时,Chrome 任务管理器截图:主要包括

  • 1 个浏览器进程
  • 1 个 GPU 进程
  • 1 个网络进程
  • 2 个渲染进程(对应一个 Tab 一个进程)
  • 4 个扩展程序进程

MAC 的活动监视器中的 chorme 进程,可以看到所有的渲染、扩展、GPU、网络进程都统一显示为 Google Chrome Helper。

这种多进程浏览器架构主要有如下优势:

  • 1. 避免单个页面奔溃影响整个浏览器
  • 2. 避免第三方插件奔溃影响整个浏览器
  • 3. 充分利用多核优势

3)浏览器的渲染进程

  • 浏览器有多个渲染进程、一个 Tab 页面一个(相同的 Tab 页面可能会被合并)
  • 一个渲染进程包含多个线程

一个渲染进程主要包括如下线程:

1.GUI 线程(主要负责解析 HTML、CSS 和渲染页面)

2.JS 引擎线程(负责解析和执行 JS 代码)

3. 事件线程(控制事件循环)

4. 定时器线程(处理定时器相关逻辑)

5. 异步请求线程(发起 Ajax 时会生成该线程)

线程规则:

1.GUI 渲染线程与 JS 引擎线程是互斥的 ,当 JS 引擎执行时 GUI 线程会被挂起,页面的更新操作会 等到 JS 引擎空闲时 执行,涉及任务和微任务相关知识

2. 一个渲染进程同时只有一个 JS 解析线程在运行

3.JS 引擎线程不停的处理事件线程推送到事件队列中的任务

4. 定时器和异步请求最终生成的回调事件也有事件线程来控制和管理

了解了浏览器的渲染进程之后我们再来看看 JS 引擎。

4)从事件循环的角度来理解 JS 引擎的工作过程

在理解什么是事件循环之前,我们先来了解下同步和异步的概念

1. 同步和异步

同步是代码执行后就可以获得想要的结果,异步是指代码执行之后不能立即获得结果,

你打电话问书店老板有没有《Javascript 权威指南》这本书,如果是同步通信机制,书店老板会说,你稍等,”我查一下 ”,然后开始查啊查,等查好了(可能是 5 秒,也可能是一天)告诉你结果(返回结果)。
而异步机制,书店老板直接告诉你我查一下啊,查好了打电话给你,然后直接挂电话了(不返回结果)。然后查好了,他会主动打电话给你。在这里老板通过“回电”这种方式来返回结果。

2. 同步任务和异步任务

同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而是由事件线程调度,在满足执行条件的时候放到事件队列中,等待主线程 (JS 线程) 的执行。

3. 为什么要有事件循环的概念

JS 包含有异步操作(如:Ajax、定时器),这些异步操作完成之后需要通知 JS 引擎来处理异步操作的返回,如 Ajax 的 Callback。这些异步操作什么时候完成是不确定的,所以需要有一个事件队列,事件线程将已经完成的异步操作的回调任务加载到事件队列中,JS 引擎在执行完当前的同步任务之后循环从事件队列中取事件执行。

异步任务:setTImeout、setInterval、Promise、process.nextTick(Node.js)、Ajax

同步任务:除以上异步任务

同步任务和异步任务的执行流程如下:

异步任务统一有事件线程管理,当异步任务完成的时候会被放入到事件队列中,JS 在顺序执行完当前的代码之后会从事件队列中读取任务,再重复整个流程,判断该任务是同步还是异步。

4. 异步任务的优先级

如果按照上述的简化理解,所有异步任务都按照满足执行条件的顺序放到事件队列中,世界很和平,先来先到,但是在 ES6 当中,引入了 microtask 的概念,microtask 会在当前的任务执行完成之后立即执行。因为我们将异步任务分为 task 和 microtask,我们又称为宏任务和微任务。

task:setTImeout、setInterval、ajax

microtask:MutationObserve、promise、process.nextTick(Node.js)

这样子加了优先级的话 JS 的执行又会变得再复杂一点,如下图所示,异步任务执行完成之后会判断他是 task 还是 microtask,再分别加到不同的时间队列中,JS 当前任务执行完成之后优先清空当前的 microtask 队列,而且在每次执行完宏任务的时候都会去清空微任务。

示例:

运行如下示例,就可以验证上述执行流程


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <div class="outer">
          <div class="inner"></div>
    </div>
    <script>
        // Let's get hold of those elements
        var outer = document.querySelector('.outer');
        var inner = document.querySelector('.inner');
        // Let's listen for attribute changes on the
        // outer element
        new MutationObserver(function() {console.log('mutate');
        }).observe(outer, {attributes: true});
        // Here's a click listener…
        function onClick() {console.log('click');
          setTimeout(function() {console.log('timeout');
          }, 0);
          Promise.resolve().then(function() {console.log('promise');
          });
          outer.setAttribute('data-random', Math.random());
        }
        // …which we'll attach to both elements
        inner.addEventListener('click', onClick);
        outer.addEventListener('click', onClick);
    </script>
</body>
</html>

5.UI 渲染线程什么时候工作

UI 渲染线程会在当前的 Task 执行完成之后,下一个 Task 执行之前执行,微任务会优先于 UI 渲染线程,这就意味着我们使用微任务更新的 DOM 能更快的被渲染出来。另外 Vue.js 最新版本数据变更的时候采用的是 promise 和 MutationObserver 创建微任务:https://github.com/vuejs/vue/…

Demo:用于理解任务和 UI 渲染之间的关系

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue nextTick</title>
</head>
<body>
    <!-- jsFiddle 例子:http://jsfiddle.net/gkmzns9u/14/ -->
    <div id="task">
        <div id="msg">
            {{msg}}
        </div>
        <div @click="greet">
            click me!
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    <script type="text/javascript">
        const vm = new Vue({
            el: '#task',
            data: {msg: 'hello'},
            methods: {greet: () => {
                    // 修改 model,触发 set 方法,调用 update 方法,添加 DOM 更新微任务
                      vm.msg  = 'hello world';
                      vm.msg  = 'hello world2';
                      // 查看 DOM,由于是异步更新 DOM,根据 EventLoop 原理可知,这里 DOM 还没有更新,// hello
                    alert(document.getElementById('msg').innerHTML);
                    // nextTick 使用 promise, 是个微任务,在当前 greet 方法执行完成之后会立即执行
                    vm.$nextTick().then(() => {
                        // 由于 DOM 更新微任务先被添加,先入先出,这里获取的 DOM 已经是更新好的
                        // hello world
                        alert(document.getElementById('msg').innerHTML);
                        // 直接修改 DOM, 同步任务
                        document.getElementById('msg').innerHTML = 'test'
                        // 立即生效
                        // test
                        alert(document.getElementById('msg').innerHTML);
                        /* 根据 HTML Standard,一轮事件循环执行结束 (包括微任务) 之后,下轮事件循环执行之前开始进行 UI render。即:macro-task 任务执行完毕,接着执行完所有的 micro-task 任务后,此时本轮循环结束,开始执行 UI render。UI render 完毕之后接着下一轮循环。*/
                    });
                    // 由于 setTimeout 为宏任务,虽然延迟时间为 0,但还是要晚于 nextTick 执行
                    // 而且可以明显看到在 setTimeout 回调执行之前页面上已经渲染上 test, 说明 UI Render 已经在 setTimeout 回调之前执行
                    setTimeout(()=>{alert('setTimeout start')
                        document.getElementById('msg').innerHTML = 'setTimeout'
                    }, 0)
                }
            }
        });
    </script>
</body>
</html>

正文完
 0