最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 如何模拟浏览器解析一段HTML

    正文概述 掘金(天诩)   2021-05-05   613

    了解来源

    浏览器通过HTTP协议接收到报文之后,我们可以通过split('\r\n');简单粗暴地把它分隔开,再取用我们需要的部分即可。
    但是这样做有一个很大的问题:HTTP传输报文是分段的,我们不能保证每次分隔都作用于完整的报文,因此这种方法不可取,那么应该如何做呢?这里就不得不提到DOM树的概念了,也就是文档对象模型(Document object model),详解可以自行搜索

    有穷状态机

    浏览器在处理一段HTML字符串的时候,就应用到了有穷状态机的思想,我个人理解是:它将处理的流程分为几个状态,每个状态都负责执行一定的功能,就像工厂的流水线一样,到什么阶段就进行什么操作,只需要将它的逻辑组织好就能实现自动化处理

    关于浏览器是如何将一段HTML解析,并且生成一颗DOM树,我想分享以下做法

    let htmlStr = `
    <html>
      <head></head>
      <body>
        <img />
        <span></span>
        <div></div>
      </body>
    </html>
    `
    // 这段就是我们要处理的HTML字符串
    let currentToken = null;
    let stack = [{ type: 'document', children: [] }]
    // 使用栈来操作匹配标签对,生成一颗DOM树
    parse(htmlStr);
    console.log(JSON.stringify(stack[0], null, 2));
    function emit(token) {
      console.log(token);
      let top = stack[stack.length - 1];
      // console.log(top);
      if (token.type === 'startTag') {
        let element = {
          type: 'element',
          tagName: token.tagName,
          children: [],
          attribute: []
        }
        stack.push(element);
        // if (!top.children) top.children = [];
        // console.log(top.children);
        top.children.push(element);
      } else if (token.type === 'endTag') {
        if (token.tagName !== top.tagName) {
          throw new Error('tagname match error')
        } else{
          stack.pop(token);
        }
      } else if (token.type === 'selfCloseTag') {
        let element = {
          type: 'element',
          tagName: token.tagName,
          children: [],
          attribute: []
        }
        top.children.push(element);
      }
      currentToken = null;
    }
    

    这部分就是有穷状态机的应用

    function parse(str) {
      let state = start;
      for (let c of str) {
        state = state(c);
      }
    }
    function start(c) {
      if (c === '<') {
        return tagOpen
      } else {
        return start
      }
    }
    function tagOpen(c) {
      if (c === '/') {
        return tagClose
      } else if (c.match(/[a-zA-Z]/)) {
        currentToken = {
          type: 'startTag',
          tagName: c
        }
        return tagName
      }
    }
    function tagName(c) {
      if (c.match(/[a-zA-Z]/)) {
        currentToken.tagName += c;
        return tagName
      } else if (c.match(/[/t/r/n ]/)) {
        return beforeAttributeName
      } else if (c === '>') {
        // 拼接结束了
        emit(currentToken);
        return start
      }
    }
    function beforeAttributeName(c) {
      if (c === '/') {
        currentToken.type = 'selfCloseTag';
        return tagName
      }
    }
    function tagClose(c) {
      if (c.match(/[a-zA-Z]/)) {
        currentToken = {
          type: 'endTag',
          tagName: c
        }
        return tagName
      }
    }
    

    这是分隔后结果

    { type: 'startTag', tagName: 'html' }
    { type: 'startTag', tagName: 'head' }
    { type: 'endTag', tagName: 'head' }
    { type: 'startTag', tagName: 'body' }
    { type: 'selfCloseTag', tagName: 'img' }
    { type: 'startTag', tagName: 'span' }
    { type: 'endTag', tagName: 'span' }
    { type: 'startTag', tagName: 'div' }
    { type: 'endTag', tagName: 'div' }
    { type: 'endTag', tagName: 'body' }
    { type: 'endTag', tagName: 'html' }
    

    这是处理完成的DOM树,其中element代表它是元素节点

    {
      "type": "document",
      "children": [
        {
          "type": "element",
          "tagName": "html",
          "children": [
            {
              "type": "element",
              "tagName": "head",
              "children": [],
              "attribute": []
            },
            {
              "type": "element",
              "tagName": "body",
              "children": [
                {
                  "type": "element",
                  "tagName": "img",
                  "children": [],
                  "attribute": []
                },
                {
                  "type": "element",
                  "tagName": "span",
                  "children": [],
                  "attribute": []
                },
                {
                  "type": "element",
                  "tagName": "div",
                  "children": [],
                  "attribute": []
                }
              ],
              "attribute": []
            }
          ],
          "attribute": []
        }
      ]
    }
    

    希望和大家交流


    起源地下载网 » 如何模拟浏览器解析一段HTML

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元