浏览器原理 21 # DOM树:JavaScript是如何影响DOM树构建的?

简介: 浏览器原理 21 # DOM树:JavaScript是如何影响DOM树构建的?

说明

浏览器工作原理与实践专栏学习笔记



什么是 DOM

从网络传给渲染引擎的 HTML 文件字节流是无法直接被渲染引擎理解的,需要将其转化为渲染引擎能够理解的内部结构,这个结构就是 DOM。


在渲染引擎中,DOM 有三个层面的作用:


   页面:DOM 是生成页面的基础数据结构


   JavaScript 脚本:DOM 提供给 JavaScript 脚本操作的接口,通过这套接口,JavaScript 可以对 DOM 结构进行访问,从而改变文档的结构、样式和内容


   安全:DOM 是一道安全防护线,过滤一些不安全的内容



DOM 树如何生成

在渲染引擎内部,有一个叫 HTML 解析器(HTMLParser)的模块,它的职责就是负责将 HTML 字节流转换为 DOM 结构。


HTML 解析器(HTMLParser)


Q:HTML 解析器是等整个 HTML 文档加载完成之后开始解析的,还是随着 HTML 文档边加载边解析的?


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


详细过程:


   网络进程接收到响应头之后,会根据响应头中的 content-type 字段来判断文件的类型是不是 HTML 类型的文件


   如果 content-type 的值是 text/html,表明是 HTML 类型的文件,然后为该请求选择或者创建一个渲染进程。


   网络进程和渲染进程之间会建立一个共享数据的管道,网络进程接收到字节流后就往这个管道里面放


   而渲染进程则从管道的另外一端不断地读取数据,并同时将读取的数据给 HTML 解析器。


   HTML 解析器会动态接收字节流,并将其解析为 DOM。


字节流转换为 DOM

20210508100038351.png


第一个阶段,通过分词器将字节流转换为 Token。

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 元素解析完成。


2021050810011395.png

第二个和第三个阶段是同步进行的,需要将 Token 解析为 DOM 节点,并将 DOM 节点添加到 DOM 树中。


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



通过例子理解 DOM 树的生成过程


例子:

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


根据上面 HTML 解析器的分析

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


1、解析到 StartTag html 时的状态

2021050810162525.png


2、解析到 StartTag div 时的状态

20210508101833332.png


3、解析出第一个文本 Token 时的状态


2021050810194579.png


4、元素弹出 Token 栈示意图


2021050810204686.png

5、最终解析结果

20210508102213170.png



JavaScript 是如何影响 DOM 生成的

例子1:

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


执行脚本时 DOM 的状态:HTML 解析器会暂停工作,JavaScript 引擎介入,并执行 script 标签中的代码

20210508102536283.png


例子2:页面中引入 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 文件的下载,这个过程会阻塞 DOM 解析。


优化 JavaScript 线程阻塞 DOM 的方法:


预解析操作


   当渲染引擎收到字节流之后,会开启一个预解析线程,用来分析 HTML 文件中包含的 JavaScript、CSS 等相关文件,解析到相关文件之后,预解析线程会提前下载这些文件。



其他策略

  1. 使用 CDN 来加速 JavaScript 文件的加载
  2. 压缩 JavaScript 文件的体积
  3. 将 JavaScript 脚本设置为异步加载,通过 async 或 defer 来标记代码



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

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


补充


渲染引擎还有一个安全检查模块叫 XSSAuditor,是用来检测词法安全的。在分词器解析出来 Token 之后,它会检测这些模块是否安全,比如是否引用了外部脚本,是否符合 CSP 规范,是否存在跨站点请求等。如果出现不符合规范的内容,XSSAuditor 会对该脚本或者下载任务进行拦截。


CSP 规范可以参考文章


HTTP 中文开发手册:CSP

Content Security Policy 入门教程




目录
相关文章
|
10月前
|
编解码 JavaScript 前端开发
【Java进阶】详解JavaScript的BOM(浏览器对象模型)
总的来说,BOM提供了一种方式来与浏览器进行交互。通过BOM,你可以操作窗口、获取URL、操作历史、访问HTML文档、获取浏览器信息和屏幕信息等。虽然BOM并没有正式的标准,但大多数现代浏览器都实现了相似的功能,因此,你可以放心地在你的JavaScript代码中使用BOM。
307 23
|
Web App开发 前端开发 JavaScript
折腾之王:JavaScript之父Brave浏览器与BAT的诞生
2015年,JavaScript之父Brendan Eich再次创业,推出Brave浏览器和加密货币Basic Attention Token(BAT),旨在颠覆传统广告行业。Brave屏蔽广告、保护隐私,加载速度快;BAT则通过奖励机制让用户、内容创作者和广告主三方受益。尽管面临用户习惯和巨头竞争的挑战,Brave已拥有超4000万月活跃用户,成为全球增长最快的隐私浏览器,引领Web3生态发展。
464 22
折腾之王:JavaScript之父Brave浏览器与BAT的诞生
|
11月前
|
JavaScript 前端开发 API
JavaScript中通过array.map()实现数据转换、创建派生数组、异步数据流处理、复杂API请求、DOM操作、搜索和过滤等,array.map()的使用详解(附实际应用代码)
array.map()可以用来数据转换、创建派生数组、应用函数、链式调用、异步数据流处理、复杂API请求梳理、提供DOM操作、用来搜索和过滤等,比for好用太多了,主要是写法简单,并且非常直观,并且能提升代码的可读性,也就提升了Long Term代码的可维护性。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
11月前
|
数据采集 消息中间件 JavaScript
浏览器渲染揭秘:从加载到显示的全过程;浏览器工作原理与详细流程
了解浏览器工作原理与流程,能有效帮助前端开发与性能优化。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
JavaScript 前端开发 数据处理
模板字符串和普通字符串在浏览器和 Node.js 中的性能表现是否一致?
综上所述,模板字符串和普通字符串在浏览器和 Node.js 中的性能表现既有相似之处,也有不同之处。在实际应用中,需要根据具体的场景和性能需求来选择使用哪种字符串处理方式,以达到最佳的性能和开发效率。
331 63
|
移动开发 JavaScript 前端开发
一些处理浏览器兼容性问题的JavaScript库
这些库在处理浏览器兼容性问题方面都有着各自的特点和优势,可以根据具体的需求和项目情况选择合适的库来使用,从而提高代码的兼容性和稳定性,为用户提供更好的体验。同时,随着浏览器技术的不断发展,还需要持续关注和学习新的兼容性解决方案。
423 58
|
算法 开发者
Moment.js库是如何处理不同浏览器的时间戳格式差异的?
总的来说,Moment.js 通过一系列的技术手段和策略,有效地处理了不同浏览器的时间戳格式差异,为开发者提供了一个稳定、可靠且易于使用的时间处理工具。
380 57
|
JavaScript 前端开发 索引
js中DOM的基础方法
【10月更文挑战第31天】这些DOM基础方法是操作网页文档结构和实现交互效果的重要工具,通过它们可以动态地改变页面的内容、样式和行为,为用户提供丰富的交互体验。
|
JSON 移动开发 JavaScript
在浏览器执行js脚本的两种方式
【10月更文挑战第20天】本文介绍了在浏览器中执行HTTP请求的两种方式:`fetch`和`XMLHttpRequest`。`fetch`支持GET和POST请求,返回Promise对象,可以方便地处理异步操作。`XMLHttpRequest`则通过回调函数处理请求结果,适用于需要兼容旧浏览器的场景。文中还提供了具体的代码示例。
260 5
在浏览器执行js脚本的两种方式
|
缓存 JavaScript 前端开发
JavaScript 与 DOM 交互的基础及进阶技巧,涵盖 DOM 获取、修改、创建、删除元素的方法,事件处理,性能优化及与其他前端技术的结合,助你构建动态交互的网页应用
本文深入讲解了 JavaScript 与 DOM 交互的基础及进阶技巧,涵盖 DOM 获取、修改、创建、删除元素的方法,事件处理,性能优化及与其他前端技术的结合,助你构建动态交互的网页应用。
563 5