说下你对DOM树的理解

548 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第7天,点击查看活动详情

核心描述

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

  • DOM 的生成:通过浏览器渲染引擎内部的 HTML 解析器,将 HTML 字节流转换为 DOM 结构

    • HTML 解析器并不是等整个文档加载完成之后再解析的,而是网络进程加载了多少数据,HTML 解析器便解析多少数据
  • DOM 生成简单过程:HTML 字节流 -> 分词器 -> DOM 节点 -> DOM 树节点

    • 通过分词器将字节流转换为 Token。(有点类似于虚拟 DOM 的结构)
    • 至于后续的第二个和第三个阶段是同步进行的,需要将 Token 解析为 DOM 节点,并将 DOM 节点添加到 DOM 树中。

知识拓展

  • JavaScript 文件的下载过程会阻塞 DOM 解析

  • 优化 JavaScript 影响 DOM 树生成的策略:

    • CDN 来加速 JavaScript 文件的加载
    • 压缩 JavaScript 文件的体积
    • 异步加载
      • defer:脚本并行加载,等待HTML解析完成之后,按照加载顺序执行脚本,执行时机在DOMContentLoaded事件派发之前
      • async:使用 async 标志的脚本文件一旦加载完成,会立即执行,执行时机不确定,仍有可能阻塞HTML解析,执行时机在load事件派发之前
  • DOM 树在浏览器内部生成是用的什么算法

    • DFS(深度优先算法):可以参考chromium 源码-dom 渲染
    • C++ 代码示例:
    // In C++
    
    // Traverse a children.
    for (Node* child = parent.firstChild(); child; child = child->nextSibling()) {
      ...
    }
    
    // ...
    
    // Traverse nodes in tree order, depth-first traversal.
    void foo(const Node& node) {
      ...
      for (Node* child = node.firstChild(); child; child = child->nextSibling()) {
        foo(*child);  // Recursively
      }
    }
    
    • 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 元素解析完成。
    • 个人理解:
      • DOM 生成流程,先解析 html 流,转成 token,完事之后在生成 node ,之后是 stack 栈的逻辑
      • 如果是 stack 栈的话,有点像 DFS 的感觉,因为 html 流里面如果一个元素嵌套了子元素,那按照代码的顺序,应该会先把子节点给 push 进去

参考资料

浏览知识共享许可协议

本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。