美文网首页
??丨一个浏览器是如何工作的

??丨一个浏览器是如何工作的

作者: littleyu | 来源:发表于2019-05-16 20:11 被阅读0次
    开局一张图,内容全靠编

    实际上,对浏览器的实现者来说,他们做的事情,就是把一个 URL 变成一个屏幕上显示的网页。

    这个过程是这样的:

    1. 浏览器首先使用 HTTP 协议或者 HTTPS 协议,向服务端请求页面;
    2. 把请求回来的 HTML 代码经过解析,构建成 DOM 树;
    3. 计算 DOM 树上的 CSS 属性;
    4. 最后根据 CSS 属性对元素逐个进行渲染,得到内存中的位图;
    5. 一个可选的步骤是对位图进行合成,这会极大地增加后续绘制的速度;
    6. 合成之后,再绘制到界面上。

    我们在开始详细介绍之前,要建立一个感性认识。我们从 HTTP 请求回来开始,这个过程并非一般想象中的一步做完再做下一步,而是一条流水线。

    从 HTTP 请求回来,就产生了流式的数据,后续的 DOM 树构建、CSS 计算、渲染、合成、绘制,都是尽可能地流式处理前一步的产出:即不需要等到上一步骤完全结束,就开始处理上一步的输出,这样我们在浏览网页时,才会看到逐步出现的页面。

    解析代码

    1. 词(token)是如何被拆分的

    首先我们来看看一个非常标准的标签,会被如何拆分:

    <p class="a">text text text</p>
    

    如果我们从最小有意义单元的定义来拆分,第一个词(token)是什么呢?显然,作为一个词(token),整个 p 标签肯定是过大了(它甚至可以嵌套)。

    那么,只用 p 标签的开头是不是合适吗?我们考虑到起始标签也是会包含属性的,最小的意义单元其实是“<p” ,所以“ <p” 就是我们的第一个词(token)。

    我们继续拆分,可以把这段代码依次拆成词(token):

    • <p“标签开始”的开始;
    • class=“a” 属性;
    • >“标签开始”的结束;
    • text text text 文本;
    • </p> 标签结束。

    2. 构建 DOM 树

    接下来我们要把这些简单的词变成 DOM 树

    在接收的同时,即开始构建 DOM 树,所以我们的主要构建 DOM 树的算法,就写在 receiveInput 当中。当接收完所有输入,栈顶就是最后的根节点,我们 DOM 树的产出,就是这个 stack 的第一项。

    为了构建 DOM 树,我们需要一个 Node 类,接下来我们所有的节点都会是这个 Node 类的实例。

    这个过程有点类似于词的拆分的过程

    举个栗子🌰来说明

    <html maaa=a >
        <head>
            <title>cool</title>
        </head>
        <body>
            <img src="a" />
        </body>
    </html>
    

    通过这个栈,我们可以构建 DOM 树:

    • 栈顶元素就是当前节点;
    • 遇到属性,就添加到当前节点;
    • 遇到文本节点,如果当前节点是文本节点,则跟文本节点合并,否则入栈成为当前节点的子节点;
    • 遇到注释节点,作为当前节点的子节点;
    • 遇到 tag start 就入栈一个节点,当前节点就是这个节点的父节点;
    • 遇到 tag end 就出栈一个节点(还可以检查是否匹配)。

    用一个 gif 来看看上述过程

    浏览器是如何把 CSS 规则应用到节点上,并给这棵朴素的 DOM 树添加上 CSS 属性的

    首先 CSS 选择器这个名称,可能会给你带来一定的误解,觉得好像 CSS 规则是 DOM 树构建好了以后,再进行选择并给它添加样式的。实际上,这个过程并不是这样的。

    我们回忆一下我们在浏览器第一节课讲的内容,浏览器会尽量流式处理整个过程。我们上一节课构建 DOM 的过程是:从父到子,从先到后,一个一个节点构造,并且挂载到 DOM 树上的,那么这个过程中,我们是否能同步把 CSS 属性计算出来呢?

    答案是肯定的。

    在这个过程中,我们依次拿到上一步构造好的元素,去检查它匹配到了哪些规则,再根据规则的优先级,做覆盖和调整。所以,从这个角度看,所谓的选择器,应该被理解成“匹配器”才更合适。

    不知道你有没有发现,这里的选择器有个特点,那就是选择器的出现顺序,必定跟构建 DOM 树的顺序一致。这是一个 CSS 设计的原则,即保证选择器在 DOM 树构建到当前节点时,已经可以准确判断是否匹配,不需要后续节点信息。

    也就是说,未来也不可能会出现“父元素选择器”这种东西,因为父元素选择器要求根据当前节点的子节点,来判断当前节点是否被选中,而父节点会先于子节点构建。

    Selector Level 4貌似出了父级选择器。。。

    排版

    浏览器进行到这一步,我们已经给 DOM 元素添加了用于展现的 CSS 属性,接下来,浏览器的工作就是确定每一个元素的位置了。我们的基本原则仍然不变,就是尽可能流式地处理上一步骤的输出。

    这可能也就是一些 css 的书写规范中把定位属性放在最后的原因把~

    在构建 DOM 树和计算 CSS 属性这两个步骤,我们的产出都是一个一个的元素,但是在排版这个步骤中,有些情况下,我们就没法做到这样了。

    尤其是表格相关排版、flex 排版和 grid 排版,它们有一个显著的特点,那就是子元素之间具有关联性。

    渲染

    也就是把模型变成位图的过程

    我们已经经历了把 URL 变成字符流,把字符流变成词(token)流,把词(token)流构造成 DOM 树,把不含样式信息的 DOM 树应用 CSS 规则,变成包含样式信息的 DOM 树,并且根据样式信息,计算了每个元素的位置和大小。

    那么,我们最后的步骤,就是根据这些样式信息和大小信息,为每个元素在内存中渲染它的图形,并且把它绘制到对应的位置。

    这里的位图就是在内存里建立一张二维表格,把一张图片的每个像素对应的颜色保存进去(位图信息也是 DOM 树中占据浏览器内存最多的信息,我们在做内存占用优化时,主要就是考虑这一部分)。

    浏览器中渲染这个过程,就是把每一个元素对应的盒变成位图。这里的元素包括 HTML 元素和伪元素,一个元素可能对应多个盒(比如 inline 元素,可能会分成多行)。每一个盒对应着一张位图。

    注意,我们这里讲的渲染过程,是不会把子元素绘制到渲染的位图上的,这样,当父子元素的相对位置发生变化时,可以保证渲染的结果能够最大程度被缓存,减少重新渲染。

    合成

    渲染过程不会把子元素渲染到位图上面,合成的过程,就是为一些元素创建一个“合成后的位图”(我们把它称为合成层),把一部分子元素渲染到合成的位图上面。

    看到这句话,我想你一定会问问题,到底是为哪些元素创建合成后的位图,把哪些子元素渲染到合成的位图上面呢?

    这就是我们要讲的合成的策略。我们前面讲了,合成是一个性能考量,那么合成的目标就是提高性能,根据这个目标,我们建立的原则就是最大限度减少绘制次数原则。

    我们举一个极端的例子。如果我们把所有元素都进行合成,比如我们为根元素 html 创建一个合成后的位图,把所有子元素都进行合成,那么会发生什么呢?

    那就是,一旦我们用 JavaScript 或者别的什么方式,改变了任何一个 CSS 属性,这份合成后的位图就失效了,我们需要重新绘制所有的元素。

    那么如果我们所有的元素都不合成,会怎样呢?结果就是,相当于每次我们都必须要重新绘制所有的元素,这也不是对性能友好的选择。

    那么好的合成策略是什么呢,好的合成策略是“猜测”可能变化的元素,把它排除到合成之外。

    举个栗子🌰

    <div id="a">
        <div id="b">...</div>
        <div id="c" style="transform:translate(0,0)"></div>
    </div>
    

    假设我们的合成策略能够把 a、b 两个 div 合成,而不把 c 合成,那么,当我执行以下代码时:

    document.getElementById("c").style.transform = "translate(100px, 0)";
    

    我们绘制的时候,就可以只需要绘制 a 和 b 合成好的位图和 c,从而减少了绘制次数。这里需要注意的是,在实际场景中,我们的 b 可能有很多复杂的子元素,所以当合成命中时,性能提升收益非常之高。

    目前,主流浏览器一般根据 position、transform 等属性来决定合成策略,来“猜测”这些元素未来可能发生变化。

    但是,这样的猜测准确性有限,所以新的 CSS 标准中,规定了 will-change 属性,可以由业务代码来提示浏览器的合成策略,灵活运用这样的特性,可以大大提升合成策略的效果。

    绘制

    绘制是把“位图最终绘制到屏幕上,变成肉眼可见的图像”的过程,不过,一般来说,浏览器并不需要用代码来处理这个过程,浏览器只需要把最终要显示的位图交给操作系统即可。

    当绘制完成时,就完成了浏览器的最终任务,把一个 URL 最后变成了一个可以看的网页图像。

    当然本文也忽略了大量中间的细节。

    相关文章

      网友评论

          本文标题:??丨一个浏览器是如何工作的

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