我不知道的浏览器工作原理(二)

文章概述:如何解析请求回来的HTML代码,DOM树又是如何构建的
在这里插入图片描述

词是如何拆分的

词的拆分
根据这样的分析,现在我们讲讲浏览器是如何用代码实现的,我们设想,代码开始从HTTP协议收到的字符流读取字符。
在接受第一个字符之前,我们完全无法判断这是哪一个词(token),随着我拼出的字符串越来越多,拼出其他的内容可能性就越来越少。
比如,假设我们接受了一个字符"<",我们一下子就知道这不是一个文本节点,之后我们再读一个字符,比如x,那么我们一下子就知道这不是注释和CDATA了,接下来我们就一直读,知道遇到“>”或者空格,这样就得到了一个完整的词(token)。
实际上,我们每读入一个字符,其实都要做一次决策,而且这些决定是跟“当前状态”有关的。在这样的条件下,浏览器工程师想要实现把字符流解析成词(token),最常见的方案就是使用状态机。
 

状态机

在这里插入图片描述
为了理解原理,用这个简单的状态机就足够说明问题了。状态机的初始状态,我们仅仅区分“<”和非“<”:

  • 如果获得的是一个非<字符,那么可以认为进入了一个文本节点;
  • 如果获得的是一个<字符,那么进入一个标签状态

不过当我们在标签状态时,则会面临一些可能性。

  • 比如下一个字符是“!”,那么可能进入了注释节点或者CDATA节点
  • 如果下一个字符是“/”,那么可以确定进入了一个结束标签。
  • 如果下一个字符是字母,那么可以确定进入了一个开始标签。
  • 如果我们要完整处理各种HTML标准中定义的东西,那么还要考虑“?”“%”等内容。

我们可以看到,用状态机做词法分析,其实正是把每个词的“特征字符”逐个拆开成独立的状态,然后再把所有词的特征字符链合起来,形成一个联通图结构
接下来就是代码实现的事情了,在js和C/C++中,实现状态机的方式大同小异:我们把每个函数当作一个状态,参数是接受的字符,返回值是下一个状态函数。
为了方便理解和实验,我们这里用js来讲解,图上的data状态大概就像下面这样:
在这里插入图片描述
在这里插入图片描述
这段代码给出了状态机的2个状态示例:data是初始状态,tagOpenState接受了一个“<”字符,来判断标签类型的状态。
这里的状态机,每一个状态是一个函数,通过“if else”来区分下一个字符做状态迁移。这里所谓的状态迁移,就是当前状态函数返回下一个状态函数。
这样,我们的状态迁移代码非常简单:

var state= data;
var char
while(char = getInput())
	state = state(char);

不论我们用何种方式来读取字符流,我们都可以通过state来处理输入的字符流,这里用循环是一个事例,真实场景中,可能是来自TCP的输出流。
词法分析器接受字符的方式很简单,就像下面这样:
在这里插入图片描述
至此,我们就把字符流拆成词(token)了。
 

DOM树的构建

接下来我们要把这些简单的词变成DOM树,这个过程我们是使用栈来实现的,任何语言几乎都有栈,我们还是用js来实现,js中的栈只要用数组就好了。

function HTMLSyntaticalParser(){
	var stack = [new HTMLDocument];
	this.receiveInput = function(){}
	this.getOutput = function(){
		return stack[0];
	}
}

我们这样来设计HTML的语法分析器,receiveInput负责接收词法部分产生的词(token),通常可以由emmitToken来调用。
在接受的同时,即开始构建DOM树,所以我们主要构建DOM树的算法,就写在receiveInput当中,当接受完所有输入,栈顶就是最后的根节点,我们的DOM树的产出,就是这个stack的第一项。
为了构建DOM树,我们需要一个Node类,接下来所有的节点都会是这个Node类的实例。
在完全符合标准的浏览器中,不一样的HTML节点对应了不同的Node的子类,我们为了简化,就不完整实现这个继承体系了,我们仅仅把Node分为Element和Text

function Element(){
	this.childNodes = [];
}
function Text(value){
	this.value = value || '';
}

前面我们的词中,以下2个是需要成对匹配的:

  • tag start
  • tag end

根据一些编译原理中常见的技巧,我们使用的栈正是用于匹配开始和结束标签的方案。
对于Text节点,我们则需要把相邻的的Text节点合并起来,我们的做法是当词(token)入栈时,检查栈顶是否是Text节点,如果是的话就合并Text节点
同样我们来看看直观的解析过程:

<html maaa=a>
	<head>
		<title>color</title>
	</head>
	<body>
		<img src="a"/>
	</body>
</html>

通过这个栈,我们可以构建DOM树:

  • 栈顶元素就是当前的节点
  • 遇到属性,就添加到当前节点
  • 遇到文本节点,如果当前节点是文本节点,则跟文本节点合并,否则入栈成为当前节点的子节点
  • 遇到注释节点,作为当前节点的子节点
  • 遇到tag start 就入栈一个节点,当前节点就是这个节点的父节点
  • 遇到tag end就出栈一个节点(还可以检查是否匹配)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qq_21439711

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值