美文网首页
输入URL到页面展示发生了什么

输入URL到页面展示发生了什么

作者: Lyan_2ab3 | 来源:发表于2020-01-22 21:16 被阅读0次

    与浏览器进程如何配合

    首先我们需要知道浏览器进程的作用和负责的功能;

    • 浏览器进程主要负责 用户交互和管理子进程 和存储功能;
    • 网络进程主要面向浏览器进程和渲染进程提供网络下载功能;
    • 渲染进程的主要职责是把从网络下载的 HTML、JavaScript、CSS、图片等资源解析为可以显示和交互的页面,渲染进程通过网络获取的内容不一定都是安全的,所以Chrome 的渲染进程是运行在沙箱内的,为了保证安全;

    后续其他的文章 会专门介绍进程之间的交互和功能

    92d73c75308e50d5c06ad44612bcb45d.png

    用户输入URL 到页面解析出来 经历哪些步骤:

    DNS 解析 http缓存讲到了DNS解析
    TCP 三次握手
    发送请求
    服务端处理请求返回HTTP报文
    浏览器解析渲染页面
    断开连接:TCP四次挥手

    用户输入URL

    • 判断输入的URL 是否符合URL 规范,如果符合地址栏根据输入的内容加上协议,组成完整的URL,比如:baidu.com> https://baidu.com
    • 用户回车或者开始搜索的时候,表示当前页面将要被替换;标签页面的图标进入的加载状态,但是页面还是旧的页面,需要等待提交文档阶段;

    URL请求过程

    • 输入URL,开始请求之后,进入页面资源请求的过程;浏览器进程通过进程之间的通信把URL -> 网络进程
    • 网络进程接收到URL;会先检查本地缓存是否缓存了该资源;
      1、如果有缓存,直接返回给浏览器进程;
      2、如果没有缓存,重新进行网络请求;

    1、DNS解析:

    • 1、浏览器会首先查看本地硬盘的 hosts 文件,看看其中有没有和这个域名对应的规则,如果有的话就直接使用 hosts 文件里面的 ip 地址和服务器建立连接。
    • 2、如果没有,浏览器会发出一个 DNS请求到本地DNS服务器 。
    • 3、本地DNS服务器会首先查询它的缓存记录,如果缓存中有此条记录,就可以直接返回结果,此过程是递归的方式进行查询。如果没有,本地DNS服务器还要向DNS根服务器进行查询
    • 4、根DNS服务器没有记录具体的域名和IP地址的对应关系,而是告诉本地DNS服务器,你可以到域服务器上去继续查询,并给出域服务器的地址。这种过程是迭代的过程。
    • 5、本地DNS服务器继续向域服务器发出请求,
    • 6、本地DNS服务器向域名的解析服务器发出请求,这时就能收到一个域名和IP地址对应关系,本地DNS服务器不仅要把IP地址返回给用户电脑,还要把这个对应关系保存在缓存中,以备下次别的用户查询时

    2、TCP连接:

    TCP 三次握手

    3、发送HTTP请求:

    一个HTTP请求报文由请求行(request line)、请求头部(header)、空行和请求实体4个部分组成。

    浏览器会构建请求头请求行,并且把和域名相关的cookie 等数据加在请求头上,向服务器发送构建请求;

    method:

    WechatIMG561.png

    headers:本质来说,就是一些名值对。首部分以下几类:通用首部、请求首部、响应首部、实体首部、扩展首部(规范中没有的自定义首部)

    4、服务器处理请求:

    20180821111206724.png

    客户端不是直接通过HTTP协议访问某网站应用服务器,而是先请求到Nginx,Nginx再请求应用服务器,然后将结果返回给客户端,这里Nginx的作用是反向代理服务器。同时也带来了一个好处,其中一台服务器万一挂了,只要还有其他服务器正常运行,就不会影响用户使用。

    5、返回响应结果:

    • 服务器接收到请求信息之后生成响应数据,服务器把响应信息返回给网路进程
    • 网络进程接收到响应信息之后,开始解析:
      • 如果网络进程解析发现返回的状态码301或者302,说明浏览器需要重新定
        向到新的url上(从响应头Location中读取);然后重新发起新的请求。
      • 如果返回的是200,则浏览器可以继续处理这个请求;

    提到响应体,我们可能要提到浏览器是怎么知道URL 请求的类型是什么?

    • Content-Type 的值决定了如何显示响应体的内容
    • 如果Content-Type 的值是 application/octet-stream 显示字节流类型,一般浏览器会按照下载类型处理该请求;
    • Content-Type 不同后续的处理也是不同的;

    渲染进程准备

    • 服务器返回响应体,并且Content-Type 类型不是下zai,这时候要进入渲染进程;
    • 每个页面都会有个渲染进程,但是如果几个页面是同一个站点一个根源(一个根域名),如果从一个页面打开了另一个新页面,而新页面和当前页面属于同一站点的话,那么新页面会复用父页面的渲染进程
    • 渲染进程准备好之后,还不能立即进入文档解析状态
    • 因为此时的文档数据还在网络进程中,并没有提交给渲染进程

    提交文档

    • 浏览器进程让 网络进程 将 收到的 HTML 数据提交给渲染进程具体步骤如下:
      1、当浏览器进程接收到网络进程的响应头数据之后,便向渲染进程发起“提交文档”的消息;
      2、渲染进程接收到“提交文档”的消息后,会和网络进程建立传输数据的“管道”;
      3、等文档数据传输完成之后,渲染进程会返回“确认提交”的消息给浏览器进程;
      4、浏览器进程收到消息之后,开始更新页面 的状态,包括了安全状态、地址栏的 URL、前进后退的历史状态,并更新 Web 页面

    判断文件类型,然后创建一个渲染进程,渲染进程准备好之后,网络进程和渲染进程之间建立一个共享数据的管道,渲染进程在另一端不断的读取数据,然后给到HTML 解析器

    渲染阶段:

    这个页面很重要,主要是渲染页面,熟悉了相关流程:
    1、就可以知道如何解决优化页面卡顿问题,优化动画流程,优化样式。
    2、我们编好的HTML JS CSS 又是如何转化成我们看到的页面的呢?
    带着问题下面看下 渲染阶段都是怎么渲染页面的:

    1、构建DOM 树

    1、通过分词器将字节流转换成Token
    2、Token 解析为DOM 节点,并将DOM 节点添加到DOM树中

    如果遇到 script 解析器会暂停 DOM 解析 ,因为js 可能要修改当前的DOM 结构。 JS 文件下载的过程中会阻塞DOM 解析

    async 和 defer 虽然都是异步的,
    1、使用 async 标志的脚本文件一旦加载完成,会立即执行;
    2、使用了 defer 标记的脚本文件,需要所有的元素解析完成之后,在 DOMContentLoaded 事件之前执行。

    如果代码引用了外部css 文件,那么执行js 之前还需要等待外部 css 文件下载完成,并解析成CSSDOM 之后才能执行js。 所以js 阻塞DOM 生成,样式又会阻塞js 的执行。

    • 因为浏览器无法直接理解和使用HTML,所以需要将浏览器转化成能够理解的结构 DOM 树
    125849ec56a3ea98d4b476c66c754f79.png
    • 构建 DOM 树的输入内容是一个非常简单的 HTML 文件,然后经由 HTML 解析器解析,最终输出树状结构的 DOM

    HTML 解析器并不是等整个文档加载完成之后在解析的,而是网络进程加载了多少数据,HTML 解析器便解析多少数据

    2、样式计算
    • 把 CSS 转换为浏览器能够理解的结构;当渲染引擎接收到 CSS 文本时,会执行一个转换操作,将 CSS 文本转换为浏览器可以理解的结构——styleSheets。
    • 转换样式表中的属性值,使其标准化
    • 计算出 DOM 树中每个节点的具体样式

    样式计算阶段的目的是为了计算出 DOM 节点中每个元素的具体样式,在计算过程中需要遵守 CSS 的继承和层叠两个规则。这个阶段最终输出的内容是每个 DOM 节点的样式,并被保存在 ComputedStyle 的结构内

    css 如何影响 首次加载的白屏时间:
    • 当渲染进程接收 HTML 文件字节流时,会先开启一个预解析线程,如果遇到 JavaScript 文件或者 CSS 文件,那么预解析线程会提前下载这些数据
    • 当遇到 css 文件,去发起css 下载,这里有个空闲时间,就是 DOM 构建结束之后,css 还未下载 , 渲染流水线无事可做,因为下一步是合成布局树,而合成布局树需要 CSSOM 和 DOM,所以这里需要等待 CSS 加载结束并解析成 CSSOM
    
    <html>
    <head>
        <link href="theme.css" rel="stylesheet">
    </head>
    <body>
        <div>geekbang com</div>
        <script src='foo.js'></script>
        <div>geekbang com</div>
    </body>
    </html>
    
    • 上面代码中 有css 外部引用和js 的外部文件,HTML 预解析器识别出来了有 CSS 文件和 JavaScript 文件需要下载,然后就同时发起这两个文件的下载请求,需要注意的是,这两个文件的下载过程是重叠的,所以下载时间按照最久的那个文件来算
    • 不管 CSS 文件和 JavaScript 文件谁先到达,都要先等到 CSS 文件下载完成并生成 CSSOM,然后再执行 JavaScript 脚本,最后再继续构建 DOM,构建布局树,绘制页面。
    优化策略:

    缩短白屏时间:
    1、通过内联 JavaScript、内联 CSS 来移除这两种类型的文件下载,这样获取到 HTML 文件之后就可以直接开始渲染流程了
    2、但并不是所有的场合都适合内联,那么还可以尽量减少文件大小,比如通过 webpack 等工具移除一些不必要的注释,并压缩 JavaScript 文件
    3、可以将一些不需要在解析 HTML 阶段使用的 JavaScript 标记上 sync 或者 defer

    3、布局阶段

    我们有 DOM 树和 DOM 树中元素的样式,但这还不足以显示页面,因为我们还不知道 DOM 元素的几何位置信息

    • 在显示之前,我们还要额外地构建一棵只包含可见元素布局树。
    • 遍历DOM 的可见节点,并把这些节点加到布局树中;
    • 不可见的节点会被布局树忽略掉,如head标签或者display:none 的标签
    • 布局计算:
      我们有了一棵完整的布局树。那么接下来,就要计算布局树节点的坐标位置了

    总结:在 HTML 页面内容被提交给渲染引擎之后,渲染引擎首先将 HTML 解析为浏览器可以理解的 DOM;然后根据 CSS 样式表,计算出 DOM 树所有节点的样式;接着又计算每个元素的几何坐标位置,并将这些信息保存在布局树中

    4、分层
    • 因为页面中有很多复杂的效果,如一些复杂的 3D 变换、页面滚动,或者使用 z-indexing 做 z 轴排序等,为了更加方便地实现这些效果,渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树
    • 浏览器的页面实际上被分成了很多图层,这些图层叠加后合成了最终的页面
    • 并不是布局树的每个节点都包含一个图层,如果一个节点没有对应的层,那么这个节点就从属于父节点的图层
    图层绘制
    • 一个图层的绘制拆分成很多小的绘制指令,然后再把这些指令按照顺序组成一个待绘制列表
    栅格化(raster)操作
    • 绘制列表只是用来记录绘制顺序和绘制指令的列表,而实际上绘制操作是由渲染引擎中的合成线程来完成的
    • 当图层的绘制列表准备好之后,主线程会把该绘制列表提交(commit)给合成线程
    • 合成线程会按照视口附近的图块来优先生成位图,所谓栅格化,是指将图块转换为位图
    • 栅格化过程都会使用 GPU 来加速生成,使用 GPU 生成位图的过程叫快速栅格化,或者 GPU 栅格化,生成的位图被保存在 GPU 内存中
    • 渲染进程把生成图块的指令发送给 GPU,然后在 GPU 中执行生成图块的位图,并保存在 GPU 的内存中
    5、 合成和显示
    • 一旦所有图块都被光栅化,合成线程就会生成一个绘制图块的命令——“DrawQuad”,然后将该命令提交给浏览器进程
    • 浏览器进程中的viz 组件 ,将其页面内容绘制到内存中,最后再将内存显示在屏幕上

    总结下渲染流程:
    1、渲染进程将 HTML 内容转换为能够读懂的 DOM 树结构
    2、渲染引擎将 CSS 样式表转化为浏览器可以理解的 styleSheets,计算出 DOM 节点的样式
    3、创建布局树,并计算元素的布局信息。
    4、对布局树进行分层,并生成分层树。
    5、为每个图层生成绘制列表,并将其提交到合成线程。
    6、合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。
    7、合成线程发送绘制图块命令 DrawQuad 给浏览器进程
    8、浏览器进程根据 DrawQuad 消息生成页面,并显示到显示器上。

    相关文章

      网友评论

          本文标题:输入URL到页面展示发生了什么

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