文章目录

  • 一、当输入url后,全过程
  • 二、页面渲染机制
  • 三、DOM
    • 1.什么是 DOM
    • 2.DOM 树如何生成
      • HTML 解析器
    • 3.JavaScript 是如何影响 DOM 生成的
      • 内嵌JavaScript
      • 引入 JavaScript 文件
      • 引入css文件
  • 四、拓展-CSS优化
    • css异步加载

一、当输入url后,全过程

一般会经历以下几个过程:

1、首先,在浏览器地址栏中输入url

2、浏览器先查看浏览器缓存-系统缓存-路由器缓存,如果缓存中有,会直接在屏幕中显示页面内容。若没有,则跳到第三步操作。

3、在发送http请求前,需要域名解析(DNS解析),解析获取相应的IP地址。

4、浏览器向服务器发起tcp连接,与浏览器建立tcp三次握手。

5、握手成功后,浏览器向服务器发送http请求,请求数据包。

6、服务器处理收到的请求,将数据返回至浏览器

7、浏览器收到HTTP响应

8、读取页面内容,浏览器渲染,解析html源码,并请求html代码中的资源(如js、css、图片等)

9、断开TCP连接

10、浏览器对页面进行渲染呈现给用户

11、ajax查询

其中,步骤2的具体过程是:

浏览器缓存:浏览器会记录DNS一段时间,因此,只是第一个地方解析DNS请求;
操作系统缓存:如果在浏览器缓存中不包含这个记录,则会使系统调用操作系统,获取操作系统的记录(保存最近的DNS查询缓存);
路由器缓存:如果上述两个步骤均不能成功获取DNS记录,继续搜索路由器缓存;
ISP缓存:若上述均失败,继续向ISP搜索。

二、页面渲染机制

1、HTML的加载

HTML是一个网页的基础,下载完成后解析

2、其他静态资源加载

  • html顺序加载,其中js会阻塞后续dom和资源加载,如果代码里引用了外部的 CSS 文件,那么在执行 JavaScript 之前,还需要等待外部的 CSS 文件下载完成,并解析生成 CSSOM 对象之后,才能执行 JavaScript 脚本。
  • 浏览器会使用prefetch对引用的资源提前下载
  • 没有defer或async,HTML的解析会停下来,浏览器会立即加载并执行指定的脚本,等JS下载完执行结束后才继续解析HTML,防止JS修改已经完成的解析结果。
  • 有async,加载和渲染后续文档元素的过程将和script.js的加载与执行并行进行(下载一部,执行同步,加载完就执行)
  • 有defer,加载后续文档元素的过程将和script.js的加载并行进行(异步),但是script.js的执行要在所有元素解析完成之后,DOMContentLoaded事件触发之前完成

3、DOM树构建

在HTML解析的同时,解析器会把解析完成的结果转换成DOM对象,再进一步构建DOM树

4、CSSOM树构建

CSS下载完之后对CSS进行解析,解析成CSS对象,然后把CSS对象组装起来,构建CSSOM树

5、渲染树构建

当DOM树和CSSOM树都构建完之后,浏览器根据这两个树构建一棵渲染树

6、布局计算

渲染树构建完成以后,浏览器计算所有元素大小和绝对位置

7、渲染

布局计算完成后,浏览器在页面渲染元素。经过渲染引擎处理后,整个页面就显示出来

构建DOM树->构建渲染树->布局渲染树:计算盒模型位置和大小->绘制渲染树

三、DOM

1.什么是 DOM

从网络传给渲染引擎的 HTML 文件字节流是无法直接被渲染引擎理解的,所以要将其转化为渲染引擎能够理解的内部结构,这个结构就是 DOM。DOM 提供了对 HTML 文档结构化的表述。
在渲染引擎中,DOM 有三个层面的作用:

  • 从页面的视角来看,DOM 是生成页面的基础数据结构。
  • 从 JavaScript 脚本视角来看,DOM 提供给 JavaScript 脚本操作的接口,通过这套接口,JavaScript 可以对 DOM 结构进行访问,从而改变文档的结构、样式和内容。
  • 从安全视角来看,DOM 是一道安全防护线,一些不安全的内容在 DOM 解析阶段就被拒之门外了。

概述:DOM 是表述 HTML 的内部数据结构,它会将 Web 页面和 JavaScript 脚本连接起来,并过滤一些不安全的内容。

2.DOM 树如何生成

HTML 解析器

在渲染引擎内部,有一个叫HTML 解析器(HTMLParser)的模块。

HTML 解释器的工作就是将网络或者本地磁盘获取的 HTML 网页和资源从字节流解释成 DOM 树结构。

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

网络进程接收到响应头之后,会根据响应头中的 content-type 字段来判断文件的类型,比如 content-type 的值是“text/html”,那么浏览器就会判断这是一个 HTML 类型的文件,然后为该请求选择或者创建一个渲染进程。渲染进程准备好之后,网络进程和渲染进程之间会建立一个共享数据的管道,网络进程接收到数据后就往这个管道里面放,而渲染进程则从管道的另外一端不断地读取数据,并同时将读取的数据“喂”给 HTML 解析器。


字节流转换为 DOM 需要三个阶段:

  • 第一个阶段,通过分词器将字节流转换为 Token。
  • 后续的第二个和第三个阶段是同步进行的,需要将 Token 解析为 DOM 节点,并将 DOM 节点添加到 DOM 树中。

HTML 解析器维护了一个Token 栈结构,该 Token 栈主要用来计算节点之间的父子关系,在第一个阶段中生成的 Token 会被按照顺序压到这个栈中。具体的处理规则如下所示:

  • 如果压入到栈中的是StartTag Token,HTML 解析器会为该 Token 创建一个 DOM 节点,然后将该节点加入到 DOM 树中,它的父节点就是栈中相邻的那个元素生成的节点。
  • 如果分词器解析出来是文本 Token,那么会生成一个文本节点,然后将该节点加入到 DOM 树中,文本 Token 是不需要压入到栈中,它的父节点就是当前栈顶 Token 所对应的 DOM 节点。
  • 如果分词器解析出来的是EndTag 标签,比如是 EndTag div,HTML 解析器会查看 Token 栈顶的元素是否是 StarTag div,如果是,就将 StartTag div 从栈中弹出,表示该 div 元素解析完成。

通过分词器产生的新 Token 就这样不停地压栈和出栈,整个解析过程就这样一直持续下去,直到分词器将所有字节流分词完成。

例子:

<html>
<body><div>1</div><div>test</div>
</body>
</html>

这段代码以字节流的形式传给了 HTML 解析器,经过分词器处理,

解析出来的第一个 Token 是 StartTag html,解析出来的 Token 会被压入到栈中,并同时创建一个 html 的 DOM 节点,将其加入到 DOM 树中

这里需要补充说明下,HTML 解析器开始工作时,会默认创建了一个根为 document 的空 DOM 结构,同时会将一个 StartTag document 的 Token 压入栈底。然后经过分词器解析出来的第一个 StartTag html Token 会被压入到栈中,并创建一个 html 的 DOM 节点,添加到 document 上,如下图所示


然后按照同样的流程解析出来 StartTag body 和 StartTag div,其 Token 栈和 DOM 的状态如下图所示:


接下来解析出来的是第一个 div 的文本 Token,渲染引擎会为该 Token 创建一个文本节点,并将该 Token 添加到 DOM 中,它的父节点就是当前 Token 栈顶元素对应的节点,如下图所示:


再接下来,分词器解析出来第一个 EndTag div,这时候 HTML 解析器会去判断当前栈顶的元素是否是 StartTag div,如果是则从栈顶弹出 StartTag div,如下图所示


按照同样的规则,一路解析,最终结果如下图所示:

3.JavaScript 是如何影响 DOM 生成的

内嵌JavaScript

例子:

<html>
<body><div>1</div><script>let div1 = document.getElementsByTagName('div')[0]div1.innerText = 'time.geekbang'</script><div>test</div>
</body>
</html>

我在两段 div 中间插入了一段 JavaScript 脚本,这段脚本的解析过程就有点不一样了。script标签之前,所有的解析流程还是和之前介绍的一样,但是解析到script标签时,渲染引擎判断这是一段脚本,此时 HTML 解析器就会暂停 DOM 的解析,因为接下来的 JavaScript 可能要修改当前已经生成的 DOM 结构。

通过前面 DOM 生成流程分析,我们已经知道当解析到 script 脚本标签时,其 DOM 树结构如下所示:


这时候 HTML 解析器暂停工作,JavaScript 引擎介入,并执行 script 标签中的这段脚本,因为这段 JavaScript 脚本修改了 DOM 中第一个 div 中的内容,所以执行这段脚本之后,div 节点内容已经修改为 time.geekbang 了。脚本执行完成之后,HTML 解析器恢复解析过程,继续解析后续的内容,直至生成最终的 DOM。

引入 JavaScript 文件

除了在页面中直接内嵌 JavaScript 脚本之外,我们还通常需要在页面中引入 JavaScript 文件,这个解析过程就稍微复杂了些,如下面代码:

//foo.js
let div1 = document.getElementsByTagName('div')[0]
div1.innerText = 'time.geekbang'
<html>
<body><div>1</div><script type="text/javascript" src='foo.js'></script><div>test</div>
</body>
</html>

执行到 JavaScript 标签时,暂停整个 DOM 的解析,执行 JavaScript 代码,不过这里执行 JavaScript 时,需要先下载这段 JavaScript 代码。这里需要重点关注下载环境,因为JavaScript 文件的下载过程会阻塞 DOM 解析,而通常下载又是非常耗时的,会受到网络环境、JavaScript 文件大小等因素的影响。

Chrome 浏览器做了很多优化,其中一个主要的优化是预解析操作。当渲染引擎收到字节流之后,会开启一个预解析线程,用来分析 HTML 文件中包含的 JavaScript、CSS 等相关文件,解析到相关文件之后,预解析线程会提前下载这些文件。

再回到 DOM 解析上,我们知道引入 JavaScript 线程会阻塞 DOM,不过也有一些相关的策略来规避,比如使用 CDN 来加速 JavaScript 文件的加载,压缩 JavaScript 文件的体积。

另外,如果 JavaScript 文件中没有操作 DOM 相关代码,就可以将该 JavaScript 脚本设置为异步加载,通过 async 或 defer 来标记代码,使用方式如下所示:

<script async type="text/javascript" src='foo.js'></script>
<script defer type="text/javascript" src='foo.js'></script>

async 和 defer 虽然都是异步的,不过还有一些差异,

  • 使用 async 标志的脚本文件一旦加载完成,会立即执行;
  • 而使用了 defer 标记的脚本文件,需要在 DOMContentLoaded 事件之前执行。

或者将 “script“ 元素放在 “body“ 元素后面。

引入css文件

引入外部css样式表

  1. 外链式

    <link type="text/css" rel="stylesheet" href="要引入的css文件的路径"/>
    
  2. 导入式
    <style type="text/css">@import url(要导入的css文件的路径);
    </style>
    

link和import导入外部样式区别:

  1. 加载顺序
    当一个页面被加载的时候(就是被浏览者浏览的时候),link引用的CSS会同时被加载,而@import引用的CSS 会等到页面全部被下载完再被加载。所以有时候浏览@import加载CSS的页面时开始会没有样式。

  2. 使用DOM控制样式差别
    当使用javascript控制dom去改变样式的时候,只能使用link标签,因为@import不是dom可以控制的.

一个例子:

<head>
<link type="text/css" rel="stylesheet" href="要引入的css文件的路径"/>
</head>
<body><div>1</div><script>let div1 = document.getElementsByTagName('div')[0]div1.innerText = 'time.geekbang' // 需要 DOMdiv1.style.color = 'red'  // 需要 CSSOM</script><div>test</div>
</body>
</html>

JavaScript 代码出现了 div1.style.color = ‘red’ 的语句,它是用来操纵 CSSOM 的,所以在执行 JavaScript 之前,需要先解析 JavaScript 语句之上所有的 CSS 样式。所以如果代码里引用了外部的 CSS 文件,那么在执行 JavaScript 之前,还需要等待外部的 CSS 文件下载完成,并解析生成 CSSOM 对象之后,才能执行 JavaScript 脚本。

JavaScript 引擎在解析 JavaScript 之前,是不知道 JavaScript 是否操纵了 CSSOM 的,所以渲染引擎在遇到 JavaScript 脚本时,不管该脚本是否操纵了 CSSOM,都会执行 CSS 文件下载,解析操作,再执行 JavaScript 脚本。

JavaScript 脚本是依赖样式表的,这又多了一个阻塞过程。

JavaScript 会阻塞 DOM 生成,而样式文件又会阻塞 JavaScript 的执行,所以在实际的工程中需要重点关注 JavaScript 文件和样式表文件,使用不当会影响到页面性能的。

  • CSS 不会阻塞 DOM 的解析,但会阻塞 DOM 渲染。
  • JS 阻塞 DOM 解析,但浏览器会"偷看"DOM,预先下载相关资源。
  • 浏览器遇到 <script>且没有defer或async属性的 标签时,会触发页面渲染,因而如果前面CSS资源尚未加载完毕时,浏览器会等待它加载完毕在执行脚本。

四、拓展-CSS优化

CSS对渲染过程的影响:

  • css加载不会阻塞DOM树的解析
  • css加载会阻塞DOM树的渲染
  • css加载会阻塞后面js语句的执行,而js又会阻塞DOM树的解析

应该尽可能的提高css加载速度,比如可以使用以下几种方法:

  1. 使用CDN(因为CDN会根据你的网络状况,替你挑选最近的一个具有缓存内容的节点为你提供资源,因此可以减少加载时间)
  2. 对css进行压缩(可以用很多打包工具,比如webpack,gulp等,也可以通过开启gzip压缩)
  3. 合理的使用缓存(设置cache-control,expires,以及E-tag都是不错的,为了在文件更新后保证能拿到最新的文件,通常在打包的时候文件名字后面加一个版本号)
  4. 减少http请求数,将多个css文件合并,或者是干脆直接写成内联样式(内联样式的一个缺点就是不能缓存)

css异步加载

  1. 通过 JS 动态插入 link 标签来异步载入 CSS 代码

    var myCSS = document.createElement( "link" );
    myCSS.rel = "stylesheet";
    myCSS.href = "mystyles.css";
    document.head.insertBefore( myCSS, document.head.childNodes[ document.head.childNodes.length - 1 ].nextSibling );
    
  2. 利用 link 上的 media 属性

media 属性规定被链接文档将显示在什么设备上。

取值:

screen   计算机屏幕(默认)。
tty 电传打字机以及类似的使用等宽字符网格的媒介。
tv  电视机类型设备(低分辨率、有限的滚屏能力)。
projection  放映机。
handheld    手持设备(小屏幕、有限带宽)。
print   打印预览模式/打印页面。
braille 盲人点字法反馈设备。
aural   语音合成器。
all 适用于所有设备。

将它设置为和用户当前浏览器环境不匹配的值,比如:media=“print”,甚至可以设置为一个完全无效的值 media=“jscourse” 之类的。浏览器就会认为这个 CSS 文件优先级非常低,就会在不阻塞的情况下进行加载。最后为了让 CSS 规则生效,再将 media 值改对。

<link rel="stylesheet" href="cssfile.css" media="jscourse" οnlοad="this.media='all'">
  1. 利用规范中新增加的 rel=“preload”

    通过 preload 属性值就是告诉浏览器这个资源文件随后会用到,请提前加载好。但是这只是加载,所以你看当它加载完毕后,还是需要将 rel 值改回去,这才能让 CSS 生效。

    看上去和第二种方法差不多,但是呢,语义上更加好一些。另外就是你仔细点就会发现 as="style"这个属性,所以 preload 不仅仅可以用在 CSS 文件上,而是可以用在绝大多数的资源文件上。比如:JS 文件

    <link rel="preload" href="scriptfile.js" as="script">
    

    然后要用的时候,就创建一个 script 标签指向它:

    var script = document.createElement("script");
    script.src = "scriptfile.js";
    document.body.appendChild(script);
    

    这个时候浏览器就直接从缓存中拿这个文件了,不会再发请求了,因为此前已经加载好了。

参考:https://blog.poetries.top/browser-working-principle/guide/part5/lesson22.html

本文链接:https://blog.csdn.net/qq_39903567/article/details/115265394

前端面试系列-输入url后全过程页面渲染机制DOM生成过程相关推荐

  1. 输入URL后浏览器的渲染过程

    铅笔头课堂,有态度的前端培训 输入URL后浏览器的渲染过程 背景 作为前端开发,浏览器是与我们日常相伴的工具,以chrome为例,我们可以利用它调试页面的element节点.network网络.con ...

  2. 前端——在浏览器输入url后发生了都发生了什么

    一个页面从输入 URL 到页面加载显示完成,这个过程中都发生了什么? 主要包括以下几个基本步骤: 浏览器的地址栏输入URL并按下回车. 浏览器查找当前URL是否存在缓存,并比较缓存是否过期. DNS解 ...

  3. 浏览器的工作原理:输入URL后,Web页面是如何呈现的?

    重学JavaScript01 ----- 浏览器工作原理 文章目录 重学JavaScript01 ----- 浏览器工作原理 前言 一. 网络模型 应用层: 传输层: TCP协议的运行流程: 网络层: ...

  4. 【网络】浏览器输入URL到展示页面全过程(含互联网协议及HTTPS简介)

    这里写自定义目录标题 前言 1.URL介绍 2.DNS查找 3.互联网协议 3.1 实体层 3.2 链路层 3.2.1 以太网协议 3.2.2 Mac地址 3.2.3 广播 3.3 网络层 3.3.1 ...

  5. 2020最全前端面试系列(浏览器原理)(最容易忽视的面试隐藏大杀器)

    2020前端面试系列(浏览器原理) 浏览器输入URL到返回页面的全过程 浏览器渲染步骤 重排和重绘 触发reflow情形 减少reflow方法 浏览器本地存储方案的比较 cookie localSto ...

  6. 浏览器渲染机制面试_【前端面试必考题】页面渲染机制(一)

    页面渲染机制这部分内容会分成两篇来进行讲解,这两篇里我们准备聊一下页面的渲染的过程,包括页面的加载.DOM 树的构建.CSSOM 树的构建.渲染树的构建和最后的渲染过程等.浏览器的渲染机制和网页的优化 ...

  7. HTTP浏览器输入URL后发生了什么

    原文:"天龙八步"细说浏览器输入URL后发生了什么   慕课大神 本文摘要: 1.DNS域名解析: 2.建立TCP连接: 3.发送HTTP请求: 4.服务器处理请求: 5.返回响应 ...

  8. 地址栏输入URL到显示页面的过程

    前言 从地址栏输入URL到显示页面都发生了什么?这是一道面试经常会考的面试题.那么下面我们就来探讨一下从你输入URL后到响应,都经历了哪些过程. 1.DNS解析 我们在用户PC中使用网页浏览器来访问外 ...

  9. 在浏览器地址栏中输入URL后发生了什么

    在浏览器地址栏中输入URL后发生了什么 基本流程: ①查询ip地址 ②建立tcp连接,接入服务器 ③浏览器发起http请求 ④服务器后台操作并做出http响应 ⑤网页的解析与渲染 详细步骤如下: 查询 ...

最新文章

  1. Custom Client Side Drag and Drop Behavior in ASP.NET AJAX
  2. 计算机技术应用参考文献,计算机毕业论文参考文献格式
  3. redis中的string
  4. 跨平台C++开源码的两种经常使用编译方式
  5. Kotlin实践(3)-入口 函数
  6. 【深度学习】从R-CNN到Mask R-CNN的思维跃迁
  7. 如何评价Python3.8新出的“:=”表达式?
  8. hdu 4280 最大流sap
  9. VirtualBox的Linux虚拟机访问Windows7的文件
  10. Java并发——线程安全
  11. 任何时候都不要轻易满仓
  12. ubuntu 的问题,我一个人使用,却显示两人登录?
  13. QA:golang redis协议同时写入报错
  14. 对一个“老”架构的重新思考
  15. 雷达的L、S、C、X波段是什么
  16. java面试题及答案2020 阿里(八)
  17. 架构图、用例图、流程图、时序图、类图
  18. 图片的后缀是什么意思
  19. AWS实战:AWS Kinesis Data Firehose
  20. LoopClosing中为什么要使用剥离尺度的sim3计算投影匹配

热门文章

  1. 苹果系统如何访问局域网中的计算机,mac前往mac共享-怎么让苹果机和普通pc在局域网里互相找到?我有一台苹果机和一台p 爱问知识人...
  2. 对于霸榜视频超分领域的VRT方法的深度解读
  3. 记一次京东数据产品经理面试
  4. 周末被马云的无人超市刷屏了
  5. python输出100以内偶数_Python求取100以内的所有偶数和奇数以及和
  6. APP的图标测试之震惊!双11快到了,你的app在偷偷更换图标?
  7. Cmake语句find_package()函数
  8. 迄今最全人脸识别开源
  9. 全网唯一:surfacepro系列用户重启或解锁后蓝牙失灵终极解决方法【绝对有效】
  10. 接地电阻测试仪测量接地电阻的规范要求