本文将尽可能的解释Webkit的HTML解释器流程及Dom模型相关的知识点

在前文介绍从输入url到页面的最终呈现的过程,其中有一步:渲染器进程获取到资源后会将html资源给到html解释器,用来生成dom节点最终生成dom树形结构的对象。那么html解释器具体做了啥呢?

总体流程:字节流被解码成字符流,字符流通过词法分析器被解释成词语,词语通过语法分析器构建成节点,最后节点组合成dom树。

1. 字节 —> 字符

收到字节流之后,解释器会根据网页内容所使用的编码格式,将字节流解析成对应的字符串。如果没有特别指定编码格式,直接进入下一步的词法分析。

2. 字符 —> 词语HTMLTokenizer

HTMLTokenizer类负责词法解析。输入字符串,输出是一个个的词语。
解释完成的词语会经过XSSAuditor安全验证,干嘛的呢?实质就是将一些可能会导致安全问题的词语过滤掉。只有通过安全验证的词语,才会继续下一步。

3. 词语 —> 节点HTMLTreebuilder类的construcTree

由词语创建节点过程中,坑会遇到js代码。这就是为什么全局执行的js无法访问dom,因为此时dom还没建完。且此时执行js甚至还要通过src下载js脚本时,会使得dom树构建停止。
所以建议js的代码,要么异步async,要么塞在body结束标签的下面。

4. 众多节点 —> dom树

dom模型的事件机制
无非就是捕获与冒泡。addEventListener这个api的第三个参数需要注意。默认为false也就是冒泡,设置为true时就走的捕获。
渲染引擎接收到一个事件时,会通过HitTest来检查,具体是哪一个元素触发的。然后根据相关参数确定事件触发流向。能准确说出下面代码的输出,基本就理解了。

// 事件捕获冒泡
    const body = document.getElementById('body')
        body.addEventListener('click', (event) => {
        console.log('body')
    })
    const div = document.getElementById('div')
        div.addEventListener('click', (event) => {
        console.log('div')
    }, true)
    const img = document.getElementById('img')
        img.addEventListener('click', (event) => {
        console.log('img')
        event.stopPropagation()
    })

shadow-dom
所谓shadow-dom就是影子dom,说白了就是对一些元素进行了封装。
拿video标签举例解释。
video标签

当我们在页面中使用了一个video标签,它自动的给我们一个如上图所示的播放器,包括进度条、音量控制、全屏按钮等小的组件但是,dom结构层面确实只有一个叫video的元素。如何做到的?
shadow-dom。video标签的内部,实际也是一坨div组成的代码,只不过内部做了层封装。
实机演示:

window.onload = function() {
  const div = document.getElementById('div')
  console.log('div>>>', div)
  // const root = div.webkitCreateShadowRoot()
  const root = div.attachShadow({ mode: 'open' });
  // const root = div.createShadowRoot()
  const img = document.createElement('img')
  img.width = '100'
  img.height = '100'
  img.src = './bigPic.png'
  root.appendChild(img)
  const shadowDiv = document.createElement('div')
  shadowDiv.innerHTML = 'this is shadow'
  root.appendChild(shadowDiv)
}

打开控制台查看发现:
Dom结构

div内部并没有img等子元素,反而是一个叫#shadow-root的东西,这就是那一层封装。
完毕。