zoukankan      html  css  js  c++  java
  • 前端工程化5js源码编译和ast

    写在前面

    前面几节大概了解了webpack的使用和执行过程,上一节我们知道了webpack的源码编译的库是acorn,那今天我们就来研究一下js编译以及抽象语法树(ast)。我们先来看一个笔试题

    问题
    将一个 html 字符串变成树的形式

    <div id="main" data-x="hello">Hello<span id="sub" /></div>
    

    这样的一串字符串变成如下的一棵树,考虑尽可能多的形式,比如自闭合标签等。

        {
          tag: "div",
          selfClose: false,
          attributes: {
            "id": "main",
            "data-x": "hello"
          },
          text: "Hello",
          children: [
            {
              tag: "span",
              selfClose: true,
              attributes: {
                "id": "sub"
              }
            }
          ]
        }
    

    先来分析一下题目,题意即将html树转化成对象树的表示形式,主要难点就是需要正确匹配到标签并进行转化成对象的属性。下面我们来开始写代码,首先我们要找到标签的匹配正则,我们参考html-parser.js,然后循环切割html字符串,再通过类似递归(在开始标签的时候入栈,在闭合标签出栈并构建)的方式构建树,具体实现如下:

    参考代码

    /**
     * 输入:'<div id="main" data-x="hello">Hello<span id="sub" /></div>'
     * 输出:
    {
      tag: "div",
      selfClose: false,
      attributes: {
        "id": "main",
        "data-x": "hello"
      },
      text: "Hello",
      children: [
        {
          tag: "span",
          selfClose: true,
          attributes: {
            "id": "sub"
          }
        }
      ]
    }
     * 
     */
    /**
     * 
      伪代码
        1. 通过正则匹配到开始标签,通过startTagOpen匹配,可以获取到开始标签tag,入栈
        2. 切割html字符串
        3. 匹配属性,通过attribute匹配,循环直至所有attribute都匹配完成,可以获取所有的attributes
        4. 切割html字符串
        5. 匹配开始标签的闭合, >或者/> ,通过startTagClose匹配,可以知道是否为自闭合selfClose
        6. 切割html字符串
        7. 匹配到子级标签的开始或者自己结束标签的第一个标示符, <, 可以获取到标签的内部文本text
        8. 切割字符串
        9. 如果是结束标签,出栈,构建对象树,可以获取到children,继续循环
        10. 如果是新的开始标签,继续循环
     */
    
    const html2Object = (htmlStr) => {
      const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/
      const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`
      const qnameCapture = `((?:${ncname}\\:)?${ncname})`
      const startTagOpen = new RegExp(`^<${qnameCapture}`)
      const startTagClose = /^\s*(\/?)>/
      const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`);
      let stack = [];
      let root;
      const matchTagStart = (element) => {
        const tagStart = htmlStr.match(startTagOpen);
        if (tagStart) {
          element.tag = tagStart[1];
          stack.push(element);
          htmlStr = htmlStr.substring(tagStart[0].length);
        }
      }
      const matchTagAttribute = (element) => {
        while (htmlStr.match(attribute)) {
          let attr = htmlStr.match(attribute);
          element.attributes[attr[1]] = attr[3];
          if (attr) htmlStr = htmlStr.substring(attr[0].length);
        }
      }
      const matchTagClose = (element) => {
        const tagClose = htmlStr.match(startTagClose);
        if (tagClose) {
          if (tagClose[0].trim() === '/>') {
            element.selfClose = true;
            const c = stack.pop();
            const p = stack.pop();
            if (p) {
              p.children.push(c);
              stack.push(p);
            }
          }
          htmlStr = htmlStr.substring(tagClose[0].length);
        }
      }
      const matchTagEnd = () => {
        const et = htmlStr.match(endTag);
        if (et) {
          const c = stack.pop();
          const p = stack.pop();
          if (p) {
            p.children.push(c);
            stack.push(p);
            root = JSON.parse(JSON.stringify(stack));
          }
          htmlStr = htmlStr.substring(et[0].length);
        }
      }
      const matchTagText = (element) => {
        const index = htmlStr.indexOf('<');
        element.text = htmlStr.substring(0, index);
        htmlStr = htmlStr.substring(index);
      }
      while (htmlStr) {
        let element = {
          tag: '',
          text: '',
          selfClose: false,
          attributes: {},
          children: [],
        }
        matchTagStart(element);
        matchTagAttribute(element);
        matchTagClose(element);
        matchTagText(element);
        matchTagEnd(element);
      }
      return root;
    }
    

    以上我们已经实现了一个简易的html模版解析方法,相当于html模版的对象表示法。当然也可以实现逆向,将html模版对象转化成dom树,这个相对比较简单。有了这个我们就会更好理解抽象语法树ast,ast即是对我们js代码的对象描述,和上面的例子是一个道理,有了这么一颗树我们会很容易对我们的代码进行静态操作。

    ast

    抽象语法树,js代码词法树型结构的表示。js代码在编译的过程中会首先解析成抽象语法树的形式。我们可以在astexplorer网站上查看js代码的ast结构。我们可以看一个简单的例子

    const print = ()=>{
      console.lot('hello world');
    } 
    print();
    

    转化成ast之后的代码变成了

    {
      "type": "Program",
      "start": 0,
      "end": 62,
      "body": [
        {
          "type": "VariableDeclaration",
          "start": 1,
          "end": 52,
          "declarations": [
            {
              "type": "VariableDeclarator",
              "start": 7,
              "end": 52,
              "id": {
                "type": "Identifier",
                "start": 7,
                "end": 12,
                "name": "print"
              },
              "init": {
                "type": "ArrowFunctionExpression",
                "start": 15,
                "end": 52,
                "id": null,
                "expression": false,
                "generator": false,
                "async": false,
                "params": [],
                "body": {
                  "type": "BlockStatement",
                  "start": 19,
                  "end": 52,
                  "body": [
                    {
                      "type": "ExpressionStatement",
                      "start": 23,
                      "end": 50,
                      "expression": {
                        "type": "CallExpression",
                        "start": 23,
                        "end": 49,
                        "callee": {
                          "type": "MemberExpression",
                          "start": 23,
                          "end": 34,
                          "object": {
                            "type": "Identifier",
                            "start": 23,
                            "end": 30,
                            "name": "console"
                          },
                          "property": {
                            "type": "Identifier",
                            "start": 31,
                            "end": 34,
                            "name": "lot"
                          },
                          "computed": false,
                          "optional": false
                        },
                        "arguments": [
                          {
                            "type": "Literal",
                            "start": 35,
                            "end": 48,
                            "value": "hello world",
                            "raw": "'hello world'"
                          }
                        ],
                        "optional": false
                      }
                    }
                  ]
                }
              }
            }
          ],
          "kind": "const"
        },
        {
          "type": "ExpressionStatement",
          "start": 54,
          "end": 62,
          "expression": {
            "type": "CallExpression",
            "start": 54,
            "end": 61,
            "callee": {
              "type": "Identifier",
              "start": 54,
              "end": 59,
              "name": "print"
            },
            "arguments": [],
            "optional": false
          }
        }
      ],
      "sourceType": "module"
    }
    

    我们发现转化之后的代码对象和数组的嵌套的树形结构,每个对象都最少有type、start、end三个属性,他们分别代表的是类型,开始列,结束列,通过对象的形式来描述源码。

    acorn与babel

    acorn是一个js解析库,能帮助我们将js解析成ast,如果想将jsx解析成ast则需要使用acorn-jsx。如果要将typescript解析成ast则需要用到babel或者typescript。

    参考

  • 相关阅读:
    HDU 5585 Numbers
    HDU 3308 LCIS
    POJ 2991 Crane
    POJ 1436 Horizontally Visible Segments
    POJ 3667 Hotel
    HaiHongOJ 1003 God Wang
    【SDOI 2008】 递归数列
    5月19日省中提高组题解
    【HDU 1588】 Gauss Fibonacci
    【POJ 3233】Matrix Power Series
  • 原文地址:https://www.cnblogs.com/xingguozhiming/p/15660014.html
Copyright © 2011-2022 走看看