Vue2.0 源码解析 003 之入门 将模板转化成ast语法树

什么是AST语法树

用于描述dom结构的一种树状的数据结构

如下图所示

AST语法树于虚拟dom的关系

如何将模板节点转换成AST

代码总览

const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`; // aa-aa
const qnameCapture = `((?:${ncname}\\:)?${ncname})`; //aa:aa
/* 
  ?: 匹配不捕获
*/
const startTagOpen = new RegExp(`^<${qnameCapture}`); // 可以匹配到标签名  [1]
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`); //[0] 标签的结束名字
//    style="xxx"   style='xxx'  style=xxx
const attribute =
  /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;
// 匹配属性的 aaa="aaa"  aa='aaa'  a =  aaa
const startTagClose = /^\s*(\/?)>/; //匹配标签结束的

export function parseHTML(html) {
  function createASTElement(tag, attrs) {
    //vue3里面支持多个根节点(外层加了一个空元素),但是vue2中只有一个根节点
    return {
      tag,
      type: 1,
      children: [],
      attrs,
      parent: null,
    };
  }
  // AST 语法树
  let root = null; //根元素
  let currentParent;
  let stack = [];
  //根据开始标签 结束标签 文本内容 生成ast语法树
  function start(tagName, attrs) {//解析开始标签
    //标签属性 
    console.log("start----------------", tagName);
    let element = createASTElement(tagName, attrs);
    if (!root) {//入果是刚开始解析,根元素不存在,就使用 当前元素作为根元素
      root = element;
    }
    currentParent = element;//让父元素等于当前元素
    stack.push(element);//并且将元素添加到stack栈中
  }
  function end(tagName) {//解析结束标签
    console.log("end----------------", tagName);
    let element = stack.pop();//解析结束将stack中的元素删除
    currentParent = stack[stack.length - 1];
    if (currentParent) {
        // 父元素和子元素关系互记
      element.parent = currentParent;
      currentParent.children.push(element);
    }
  }
  function chars(text) {//解析文本标签
    console.log("chars----------------", text);
    // 将文本添加到父元素中
    text = text.replace(/\s/g, "");
    if (text) {
      currentParent.children.push({
        type: 3,//元素节点的 nodeType为 1 文本节点的nodeType 为 3
        text,
      });
    }
  }
  function advance(n) {
    //将 html中处理过的 开始标签 属性  文本 结束标签 删除掉
    //整个处理逻辑是  将处理过的html字符串删除,处理剩余的,知道全部处理完成 html的值为空 停止while循环
    html = html.substring(n);
  }
  function parseStartTag() {
    let start = html.match(startTagOpen);
    // 通过正则 捕获到开始标签的信息
    if (start) {
      console.log("start", start);

      let match = {
        tagName: start[1],//标签名
        attrs: [],
      };
      advance(start[0].length); //开始标签已经处理完成 将其删除
      // 查找属性
      let end, attr;
      //不是开头标签结尾就一直解析
      while (
        !(end = html.match(startTagClose)) &&
        (attr = html.match(attribute))
      ) {
        advance(attr[0].length);
        match.attrs.push({
          name: attr[1],
          value: attr[2] || attr[3] || attr[4] || true,
        });
      }
      if (end) {
        advance(end[0].length);
        return match;
      }
    }
  }
  //
  while (html) {
    // 一层一层解析
    let text;
    let textEnd = html.indexOf("<"); //看尖角号的位置 可能是开始标签,也可能是结束标签
    if (textEnd == 0) {
      let startTagMatch = parseStartTag();
      console.log("startTagMatch", startTagMatch);
      if (startTagMatch) {
        //开始标签
        console.log("开始标签", startTagMatch.tagName);
        start(startTagMatch.tagName, startTagMatch.attrs);
        continue;
      }
      // 结束标签
      let endTagMatch = html.match(endTag);
      if (endTagMatch) {
        //匹配到结尾了
        advance(endTagMatch[0].length);
        end(endTagMatch[1]);
        console.log("结尾", endTagMatch[1]);
        continue;
      }
    }
    if (textEnd > 0) {
      //开始解析文本
      text = html.substring(0, textEnd);
    }
    if (text) {
      advance(text.length);
      chars(text);
      console.log("文本", text);
    }
    // break;
  }
  console.log("html000000000000000000000000", html);
  return root
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值