都知道浏览器和服务端是通过 HTTP 协议进行数据传输的,而 HTTP 协议又是纯文本协议,那么浏览器在得到服务端传输过来的 HTML 字符串,是如何解析成真实的 DOM 元素的呢,也就是我们常说的生成 DOM Tree,最近了解到状态机这样一个概念,于是就萌生一个想法,实现一个 innerHTML 功能的函数,也算是小小的实践一下。
函数原型
我们实现一个如下的函数,参数是 DOM 元素和 HTML 字符串,将 HTML 字符串转换成真实的 DOM 元素且 append 在参数一传入的 DOM 元素中。
function html(element, htmlString) { // 1. 词法分析 // 2. 语法分析 // 3. 解释执行}
在上面的注释我已经注明,这个步骤我们分成三个部分,分别是词法分析、语法分析和解释执行。
词法分析
词法分析是特别重要且核心的一部分,具体任务就是:把字符流变成 token 流。
词法分析通常有两种方案,一种是状态机,一种是正则表达式,它们是等效的,选择你喜欢的就好。我们这里选择状态机。
首先我们需要确定 token 种类,我们这里不考虑太复杂的情况,因为我们只对原理进行学习,不可能像浏览器那样有强大的容错能力。除了不考虑容错之外,对于自闭合节点、注释、CDATA 节点暂时均不做考虑。
接下来步入主题,假设我们有如下节点信息,我们会分出哪些 token 来呢。
<p class="a" data="js">测试元素</p>
对于上述节点信息,我们可以拆分出如下 token
开始标签:<p 属性标签:class="a" 文本节点:测试元素 结束标签:</p>状态机的原理,将整个 HTML 字符串进行遍历,每次读取一个字符,都要进行一次决策(下一个字符处于哪个状态),而且这个决策是和当前状态有关的,这样一来,读取的过程就会得到一个又一个完整的 token,记录到我们最终需要的 tokens 中。
万事开头难,我们首先要确定起初可能处于哪种状态,也就是确定一个 start 函数,在这之前,对词法分析类进行简单的封装,具体如下
function HTMLLexicalParser(htmlString, tokenHandler) { this.token = []; this.tokens = []; this.htmlString = htmlString this.tokenHandler = tokenHandler}
简单解释下上面的每个属性
token:token 的每个字符 tokens:存储一个个已经得到的 token htmlString:待处理字符串 tokenHandler:token 处理函数,我们每得到一个 token 时,就已经可以进行流式解析我们可以很容易的知道,字符串要么以普通文本开头,要么以 < 开头,因此 start 代码如下
HTMLLexicalParser.prototype.start = function(c) { if(c === '<') { this.token.push(c) return this.tagState } else { return this.textState(c) }}
新闻热点
疑难解答
图片精选