美文网首页web之路让前端飞
【九】浏览器:一个浏览器是如何工作的?(阶段二)

【九】浏览器:一个浏览器是如何工作的?(阶段二)

作者: alanwhy | 来源:发表于2019-02-18 20:58 被阅读0次

今天我们主要看两个过程:如何解析请求回来的HTML代码,DOM树又是如何构建的。

image.png

解析代码

HTML的结构不算太复杂,我们日常开发需要的90%的“词”(指编译原理的术语token, 表示最小的有意义的单元),种类大约只有标签开始、属性、标签结束、注释、CDATA 节点几种。

实际上有点麻烦的是,由于HTML 跟SGML的千丝万缕的联系,我们需要做不少容错处理。“<?”和“<%”什么的也是必须要支持好的,报了错也不能吭声。

1、词(token)是如何被拆分的
<p class="a">text text text</p>

我们可以把这段代码依次拆成词(token):

  • <p“标签开始”的开始
  • class="a" 属性
  • > “标签开始”的结束
  • text text text 文本
  • </p> 标签结束。

类似的还有

image.png

根据这样的分析,现在我们讲讲浏览器是如何用代码实现,我们设想,代码开始从HTTP协议收到的字符流读取字符。

在接受第一个字符之前,我们完全无法判断这是哪一个词(token) , 不过,随着我们接受的字符越来越多,拼出其他的内容可能性就越来越少。

比如,假设我们接受了一个字符“<”我们一下子就知道这不是一个文本节点啦。

之后我们再读一个字符,比如就是x,那么我们一下子就知道这不是注释和CDATA 了,接下来我们就一直读,直到遇到“>”或者空格,这样就得到了一个完整的词(token) 了。

实际上,我们每读入一个字符,其实都要做一次决策,而且这些决定是跟“当前状态”有关的。在这样的条件下,浏览器工程师要想实现把字符流解析成词(token) ,最常见的方案就是使用状态机。

2、状态机

我们把部分词(token)的解析画成一个状态机看看:


image.png

状态机的初始状态,我们仅仅区分“<”和“非<”

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

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

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

在JS中实现状态机的方式与C/C++的方式大同小异:我们把每个函数当作一个状态,参数是接受的字符,返回值是下一个状态函数。

便于理解,我们用JS来讲解,图上data状态大概就像下面:

var data = function(c){
    if(c=="&") {
        return characterReferenceInData;
    }
    if(c=="<") {
        return tagOpen;
    }
    else if(c=="\0") {
        error();
        emitToken(c);
        return data;
    }
    else if(c==EOF) {
        emitToken(EOF);
        return data;
    }
    else {
        emitToken(c);
        return data;
    }
};
var tagOpenState = function tagOpenState(c){
    if(c=="/") {
        return endTagOpenState;
    }
    if(c.match(/[A-Z]/)) {
        token = new StartTagToken();
        token.name = c.toLowerCase();
        return tagNameState;
    }
    if(c.match(/[a-z]/)) {
        token = new StartTagToken();
        token.name = c;
        return tagNameState;
    }
    if(c=="?") {
        return bogusCommentState;
    }
    else {
        error();
        return dataState;
    }
};
//……

data即为初始状态,tagOpenState是接受了一个“<”字符,来判断标签类型的状态

通过if else来区分下一个字符做状态迁移,即当前状态函数返回下一个状态函数。

状态迁移的代码:

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

词法分析器接受字符的方式很简单,如下:

function HTMLLexicalParser(){

    // 状态函数们……
    function data() {
        // ……
    }

    function tagOpen() {
        // ……
    }
    // ……
    var state = data;
    this.receiveInput = function(char) {
        state = state(char);
    }
}

至此,我们就把字符流拆成了词(token)了

构建DOM树

我们用栈来实现,毕竟JS中的栈只要用数组就好

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

receiveInput负责接收2词法部分产生的词(token),通常可以由emmitToken来调用

在接收的同时,即开始构建DOM树,所以我们的主要构建DOM树的算法,就写在receiveInput当中。当接收完所有输入,栈顶就是最后的根节点,我们DOM树的产出,就是这个stack的第一项。

为了构建DOM树,我们需要一个Node类,接下来我们所有的节点都会是这个Node 类的实例。

在完全符合标准的浏览器中,不一样的HTML节点对应了不同的Node的子类,我们为了简化,就不完整实现这个继承体系了。我们仅仅把Node分为Element 和Text (如果是基于类的OOP的话,我们还需要抽象工厂来创建对象)

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

根据一些编译原理中常见的技巧,我们使用的栈正是用于匹配开始和结束标签的方案。

对于Text 节点,我们则需要把相邻的Text节点合并起来,我们的做法是当词(token)入栈时,检查栈顶是否是Text 节点,如果是的话就合并Text 节点同样我们来看看直观的解析过程

同样我们来直观的看看解析过程:

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

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

  • 栈顶元素就是当前节点;
  • 遇到属性,就添加到当前节点
  • 到文本节点,如果当前节点是文本节点,则跟文本节点合并,否则入栈成为当前节点的子节点
  • 遇到注释节点,作为当前节点的子节点
  • 遇到tag start 就入栈一个节点,当前节点就是这个节点的父节点
  • 遇到tag end 就出栈一个节点(还可以检查是否匹配)

参考原文:浏览器:一个浏览器是如何工作的?(阶段二)

相关文章

  • 【九】浏览器:一个浏览器是如何工作的?(阶段二)

    今天我们主要看两个过程:如何解析请求回来的HTML代码,DOM树又是如何构建的。 解析代码 HTML的结构不算太复...

  • 网站 JS 调用尽量放到网页底部的缘由

    参考文档 网站为什么 JS 调用尽量放到网页底部? 浏览器是如何工作的?(工作原理) 浏览器是如何工作的? 浅谈s...

  • 【八】浏览器:一个浏览器是如何工作的?(阶段一)

    对浏览器的实现者来说,他们做的事情,就是把一个URL变成一个屏幕上显示的网页。 这个过程是这样的:1.浏览器首先使...

  • CSS入门的若干思考

    CSS实际上如何工作? 当浏览器显示文档时,它必须将文档的内容与其样式信息结合。它分两个阶段处理文档:浏览器将HT...

  • 浏览器缓存机制

    前言 对于浏览器缓存,‘一般人’只知道"浏览器有缓存",但是不清楚浏览器的缓存机制。那么浏览器缓存到底是如何工作的...

  • 一个浏览器是如何工作的?

    一个浏览器到底是如何工作的。 浏览器首先使用 HTTP 协议或者 HTTPS 协议,向服务端请求页面; 把请求回来...

  • 自动化测试之浏览器的启动

    自动化测试测试的一个比较重要的也是最开始的阶段,就是如何启动浏览器 三大主流的浏览器:ie,火狐,谷歌 selen...

  • HTML5简介

    什么是浏览器不同的浏览器有不同的浏览器内核, 浏览器内核也就是浏览器所采用的渲染引擎,渲染引擎决定了浏览器如何显示...

  • 把url变成有个屏幕上显示的网页

    一个浏览器是如何工作的? http协议 浏览器首先要做的事就是根据 URL 把数据取回来,取回数据使用的是 HTT...

  • 浏览器是如何工作的?

    0. 为什么要了解浏览器是如何工作的 想要写出一个最佳实践的页面,要实现性能优化,就要好好了解了解浏览器的工作原理...

网友评论

    本文标题:【九】浏览器:一个浏览器是如何工作的?(阶段二)

    本文链接:https://www.haomeiwen.com/subject/uivzeqtx.html