Skip to content

Vue 之抽象语法树

Compile 模块流程

  • 模版语法(字符串) -> 抽象语法树(JS对象) -> 渲染函数(h函数) -> 虚拟节点(vnode) -> diff & patch -> 界面(html)
  • 抽象语法 树作为 模版语法虚拟节点 之间的中介。

基础算法

递归

  • 输入 [1, [2, [3, 4]], 5],输出 { children: [ { value: 1 }, { children: [ { value: 2 }, { children: [ { value: 3 }, { value: 4 }, ] }, ] }, { value: 5 } ] }
const arr =  [1, [2, [3, 4]], 5];

function convert(item) {  
  return Array.isArray(item)
    ? { children: item.map(i => convert(i)) }
    : { value: item }
}

console.log(convert(arr));

堆栈

  • 实现 smartRepeat,输入 2[ab],输出 abab
  • 输入 2[3[a2[b]]1[cd]],输出 abbabbabbcdabbabbabbcd
const str = `2[3[a2[b]]1[cd]]`;

function smartRepeat(str) {
  const stack1 = []; // 存放数字
  const stack2 = ['']; // 存放字符串

  let i = 0;
  while(i < str.length) {
    const rest = str.substr(i);
    if (rest.match(/^\d+/)) {
      // 匹配数字
      const x = rest.match(/\d+/)[0];
      stack1.push(Number(x));
      stack2.push('');
      i += x.length;
    } else if (rest.match(/^[a-z]+/)) {
      // 匹配单词
      const x = rest.match(/^[a-z]+/)[0];
      stack2[stack2.length - 1] += x ;
      i += x.length;
    } else if (']' === rest[0]) {
      // 匹配弹栈
      const times = stack1.pop();
      const substr = stack2.pop();
      stack2[stack2.length - 1] += substr.repeat(times);
      i++;
    } else {
      // 匹配其他
      i++;
    }
  }

  return stack2[0];
}

console.log(smartRepeat(str));

手动实现

const templateStr = `<div>
  <h3>标题</h3>
  <ul>
    <li>列表行1</li>
    <li>列表行2</li>
    <li>列表行3</li>
  </ul>
</div>`;

const ast = parse(templateStr);
console.log(ast);
const startRegExp = /^\<([a-z]+[1-6]?)\>/;
const endRegExp = /^\<\/([a-z]+[1-6]?)\>/;
const wordRegExp = /^([^\<]+)\<\/[a-z]+[1-6]?\>/;

const stack1 = [];
const stack2 = [[]];

function parse(templateStr) {
  let index = 0;

  while(index < templateStr.length) {
    const rest = templateStr.substr(index);
    if (startRegExp.test(rest)) {
      // 匹配开始标签
      const tag = rest.match(startRegExp)[1];
      console.log('开始标签:<>', tag);

      stack1.push(tag);
      stack2.push([]);

      index += tag.length + 2;
    } else if (endRegExp.test(rest)) {
      // 匹配结束标签
      const tag = rest.match(endRegExp)[1];
      console.log('结束标签:</>', tag);

      const tagStart = stack1.pop();
      if (tag !== tagStart) { throw new Error('标签未匹配'); }
      const content = stack2.pop();
      stack2[stack2.length - 1].push({
        "type": "Element",
        "name": tagStart,
        "attributes": [],
        "children": content
      });

      index += tag.length + 3;
    } else if (wordRegExp.test(rest)) {
      // 匹配文本
      const word = rest.match(wordRegExp)[1];

      if (!/^\s+$/.test(word)) {
        console.log('文本:', word);
        stack2[stack2.length - 1].push({
          "type": "Text",
          "data": word
        });
      }

      index += word.length;
    } else {
      index++;
    }
  }

  return {
    "type": "Fragment",
    "children": stack2[0]
  };
}