Árvore de sintaxe abstrata do princípio de compilação babel ao código-fonte vue

imagem.png

o que é babel

  • Babel é um compilador de javascript. Desde o nascimento do ECMAScript, ele atua como um tradutor de código e ambiente operacional, permitindo-nos usar a nova sintaxe de js para escrever o código que quisermos.
  • babel é uma cadeia de ferramentas
  • O que o babel pode fazer pelos desenvolvedores?
    1. conversão de sintaxe;
    2. conversão de código-fonte;
    3. Adicione recursos ausentes no ambiente de destino por meio de polyfill (transforme e polyfill introduzindo módulos polyfill de terceiros);
    4. O uso do Babel para operar no código-fonte pode ser usado para análise estática. As mais utilizadas em combate real são: internacionalização automática, geração automática de documentos, enterramento automático, interpretador js, etc.;

O processo de compilação de babel

Analisar

  • implemento
  • Chame o parser para percorrer todos os plugins
  • Encontre o compilador parserOverride
  • Um analisador que converte o código js em uma árvore de sintaxe abstrata (AST para abreviar)

Transformar

  • pré (inicializar o objeto de operação ao entrar no AST)
  • visitante (executar o método no visitante após inicializar o objeto de operação, usar traverse para percorrer os nós AST e executar o plug-in durante o processo de passagem)
  • post (excluir objeto de operação ao sair do AST)

Gerar

  • Após a conclusão da conversão AST, o código é regenerado por gerar
  • Após a geração do código-alvo, o código relacionado ao sourceMap também será gerado ao mesmo tempo

A árvore de sintaxe abstrata do código-fonte Vue real

Para vue normal, todos nós escrevemos a sintaxe do modelo. Se queremos compilá-lo na sintaxe html normal, é muito difícil compilá-lo diretamente, então precisamos usar a árvore de sintaxe abstrata para inverter, ou seja, primeiro temos que altere _sintaxe do modelo_ para AST , após a conclusão, transforme o AST em sintaxe HTML normal e a árvore de sintaxe abstrata atuará como uma transição intermediária, facilitando o trabalho de compilação.
Esta é a sintaxe do modelo

insira a descrição da imagem aqui


Esta é a sintaxe HTML normal

insira a descrição da imagem aqui


Esta é a árvore de sintaxe abstrata que precisa ser analisada em

insira a descrição da imagem aqui


A camada inferior do Vue examina todos os modelos a partir da perspectiva de strings. Se você olhar dessa maneira, o AST analisado é como uma versão js de objetos HTML. A árvore de sintaxe abstrata é amplamente usada na compilação de modelos, ou seja, desde que como há um lugar para compilar modelos Uma árvore de sintaxe abstrata será definitivamente usada.
Qual é a relação entre árvores de sintaxe abstrata e nós virtuais?
Sintaxe do modelo => árvore de sintaxe abstrata => função de renderização (função h) => nó virtual => interface

Reserva de algoritmo necessária

ideia do ponteiro AST

【例1】试寻找字符串中,连续重复次数最多的字符‘aaaaaaaaaabbbbbbbbcccddd’
解:
var str = 'aaaaaaaaaaabbbccccccddd';
//指针
var i = 0;
var j = 1;
// 当前重复次数最多的次数
var maxRepeatCount = 0;
// 重复次数最多的字符串
var maxRepeatChar = '';
//当i还在范围内的时候,应该继续寻找
while (i <= str.length - 1) {
    
    
  //看i指向的字符和j指向的字符是不是不相同
  if (str[i] != str[j]) {
    
    
    console.log(i + '和' + j + '之间文字连续相同' + '字母' + str[i] + '它重复了' + (j - i) + '次')
    //和当前重复次数最多的进行比较
    if (j - i > maxRepeatCount) {
    
    
      // 如果当前文字重复次数(j - i)超过了此时的最大值
      // 就让它成为最大值
      maxRepeatCount = j - i;
      // 将i指针只想的字符串存为maxRepeatChar
      maxRepeatChar = str[i];
    }
    // 让指针i追上指针j
    i = j;
  }
  //不管相不相同,j永远要后移
  j++;
}

Idéias para solução de problemas:
A primeira coisa que vem à mente é tirar todas as substrings e comparar se o comprimento é o mesmo, mas, neste caso, o número de ciclos será maior e isso desperdiçará eficiência. Muitas strings que não são mais repetidos também serão calculados Vá para comparação, então apresentarei o método do ponteiro.
O ponteiro é o subscrito, não o ponteiro na linguagem c, o ponteiro na linguagem c pode operar a memória e o ponteiro no js é uma posição na tabela abaixo.
i: 0
j: 1
Se as palavras apontadas por i e j são as mesmas, então i não se move ej se move para trás
. Se as palavras apontadas por i e j são diferentes, significa que as palavras entre elas são consecutivamente o mesmo, deixe i seguir para cima j, j se move para trás.
resultado da operação:

Entre 0 e 11 a letra a se repete 11 vezes entre
11 e 14 b se repete 3 vezes
entre 14 e 20 se repete 6 vezes
entre 20 e 23 Texto com a mesma letra d seguida se repete 3 vezes

pensamento recursivo

【例1】斐波那契数列,求前N项的和
解:
function fib(n) {
    
    
  return n == 0 || n == 1 ? 1 : fib(n - 1) + fib(n - 2)
}
这其实很容易,但是衍生出来一个思考,代码是否有大量重复计算?应该怎样解决重复计算的问题。
缓存思想用hasOwnProperty方法判断
这样的话总的递归次数减少了,只要命中了缓存,就直接读缓存,不会再引发下一次递归了
解:
var cache = {
    
    }
function fib(n) {
    
    
  if (cache.hasOwnProperty(n)) {
    
    
    return cache[n]
  }
  var v = n == 0 || n == 1 ? 1 : fib(n - 1) + fib(n - 2)
  cache[n] = v;
  return v;
}
【例2】将高维数组 [1, 2, [3, [4, 5], 6], 7, [8], 9] 转换为以下这个对象
{
    
    
  children: [
    {
    
     value: 1 },
    {
    
     value: 2 },
    {
    
    
      children: [
        {
    
     value: 3 },
        {
    
    
          children: [
            {
    
     value: 4 },
            {
    
     value: 5 }
          ]
        },
        {
    
     value: 6 }
      ]
    },
    {
    
     value: 7 },
    {
    
    
      children: [
        {
    
     value: 8 }
      ]
    },
    {
    
     value: 9 }
  ]
}

解:
var arr = [1, 2, 3, [4, 5, 6]]
function convert(arr) {
    
    
  var result = [];
  for (let i = 0; i < arr.length; i++) {
    
    
    if (typeof arr[i] === 'number') {
    
    
      result.push({
    
    
        value: arr[i]
      })
    } else if (Array.isArray(arr[i])) {
    
    
      result.push({
    
    
        children: convert(arr[i])
      })
    }
  }
  return result;
}

// 还有一种更秒的写法不用循环数组,大大减少了递归次数
function convert(item) {
    
    
  var result = [];
  if (typeof item === 'number') {
    
    
    return {
    
    
      value: item
    }
  } else if (Array.isArray(item)) {
    
    
    return {
    
    
      children: item.map(_item => convert(_item))
    }
  }
  return result;
}

estrutura de pilha

Todos nós sabemos que uma matriz é uma estrutura linear e os dados podem ser inseridos e excluídos em qualquer posição. Mas, às vezes, para alcançar certas funções, devemos limitar essa arbitrariedade. Pilhas e filas são estruturas lineares restritas comuns.
Uma pilha é uma lista linear restrita, o último a entrar, o primeiro a sair. A restrição é que as operações de inserção e exclusão só são permitidas em uma extremidade da tabela, que é chamada de topo da pilha, e a outra extremidade é chamada de base da pilha. LIFO (último a entrar, primeiro a sair) significa que é o elemento que entra depois, e o espaço da pilha é retirado primeiro. Inserir um novo elemento em uma pilha também é chamado de push, push ou push.É colocar o novo elemento no topo do elemento superior da pilha para torná-lo um novo elemento superior da pilha. A exclusão de elementos de uma pilha também é conhecida como empilhamento ou desempilhamento.Ele exclui o elemento superior da pilha e torna seu elemento adjacente o novo elemento superior da pilha.

【例1】smartRepeat智能重复字符串问题
将 '3[abc]' 变为 'abcabcabc''3[2[a]2[b]]' 变成 'aabbaabbaabb''2[1[a]3[b]2[3[c]4[d]]]' 变成 'abbbcccddddcccddddabbbcccddddcccdddd'
function smartRepeat(templateStr) {
    
    
  // 指针下标
  let index = 0
  // 栈一,存放数字
  let stack1 = []
  // 栈二,存放需要重复的字符串
  let stack2 = []
  let tailStr = templateStr
  // 为啥遍历的次数为 length - 1 ? 因为要估计忽略最后的一个 ] 字符串
  while (index < templateStr.length - 1) {
    
    
    // 剩余没处理的字符串
    tailStr = templateStr.substring(index)
    if (/^\d+\[/.test(tailStr)) {
    
    
      // 匹配 "[" 前的数字
      let word = tailStr.match(/^(\d+)\[/)[1]
      // 转为数字类型
      let num = Number(word)
      // 入栈
      stack1.push(num)
      stack2.push('')
      index++
    } else if (/^\w+\]/.test(tailStr)) {
    
    
      // 匹配 "]" 前的需要重复的字符串
      let word = tailStr.match(/^(\w+)\]/)[1]
      // 修改栈二栈顶的字符串
      stack2[stack2.length - 1] = word
      // 让指针后移,word的长度,避免重复计算字符串
      index += word.length
    } else if (tailStr[0] === ']') {
    
    
      // 遇到 [ 字符串就需要出栈了,栈一和栈二同时出栈,栈二出栈的字符串重复栈一出栈的 数字的次数,并赋值到栈二的新栈顶上
      let times = stack1.pop()
      let word = stack2.pop()
      stack2[stack2.length - 1] += word.repeat(times)
      index++
    } else {
    
    
      index++
    }
    // console.log('tailStr', tailStr)

    // console.log('index', index)

    // console.log('stack1', stack1)

    // console.log('stack2', stack2)

  }
  // while结束之后, stack1 和 stack2 中肯定还剩余1项,若不是,则用户输入的格式错误
  if (stack1.length !== 1 || stack2.length !== 1) {
    
    
    throw new Error('输入的字符串有误,请检查')
  } else {
    
    
    return stack2[0].repeat(stack1[0])
  }
}

Ideias para resolver problemas:

iterar através de cada caractere

  • criar duas pilhas
  • Se o caractere for um número, coloque o número na pilha 1 e uma string vazia na pilha 2
  • Se este caractere for uma letra, mude o item do topo da pilha 2 para esta letra neste momento
  • Se o caractere for ], então o número é retirado da pilha 1 e o elemento superior da pilha 2 é repetido o número de vezes que o número é retirado da pilha 1, a pilha 2 é retirada e unida ao novo topo da pilha 2


A preparação preliminar acabou, vamos usar "sonho para iluminar a realidade" para converter **string modelo** em estrutura de árvore AST

Implementação manuscrita da árvore de sintaxe abstrata AST

insira a descrição da imagem aqui


Precisamos analisar a string de modelo em
ideias de solução de problemas AST:

  1. Analisando o atributo attribute na tag html, o pensamento da pilha é muito útil ao analisar a string do modelo e pode analisar rapidamente o HTML aninhado
  2. Converta a string do modelo em uma estrutura de árvore AST. O algoritmo usado é a pilha e a ideia da pilha na reserva do algoritmo é usada.

Analisar o atributo de atributos na tag html

export default function (attrsString) {
    
    
  let result = []
  if (!attrsString) {
    
    
    return result
  } else {
    
    
    // console.log('attrsString', attrsString)
    // 案例 'class="box" title="标题" data-type="3"'
    let isMatchQuot = false // 是否遇到引号
    // 改变了一下写法,采用双指针来记录 "" 之间走过的字符串
    let i = 0
    let j = 0

    while (j < attrsString.length) {
    
    
      // 当前指针指向的这一项
      const char = attrsString.charAt(j)
      if (char === '"') {
    
    
        // 匹配 " 字符
        isMatchQuot = !isMatchQuot
      } else if (!isMatchQuot && char === ' ') {
    
    
        // 没匹配到 " 字符,而且当前项是空格

        // 尝试拿到 i 和 j 指针之间的目标字符串
        const target = attrsString.substring(i, j).trim()
        // console.console.log(target);
        if (target) {
    
    
          result.push(target)
        }

        // 让指针i 追上 指针j
        i = j
      }
      j++
    }
    // 循环结束,还剩下最后一项属性
    result.push(attrsString.substring(i).trim())

    // filter过滤空字符 
    return result.filter(item => item).map(item => {
    
    
      const res = item.match(/^(.+)="(.+)"$/)
      return {
    
    
        name: res[1],
        value: res[2]
      }
    })
  }
}

Ideia:
Para o atributo attrs, remova os espaços em ambos os lados e, em seguida, use o ponteiro para julgar e interceptar o conteúdo de cada atributo por vez e retornar o valor da chave na forma de um objeto.

Converter string de modelo em estrutura de árvore AST

import parseAttribute from './parseAttribute'

export default function parse(templateString) {
    
    
  let index = 0
  // 未处理的字符串
  let tailStr = templateString
  // 匹配开始的html标签
  const startTagRegExp = /^\<([a-z]+[1-6]?)(\s[^\<]+)?\>/
  // 匹配结束的html标签
  const endTagRegExp = /^\<\/([a-z]+[1-6]?)\>/
  // 抓取结束标签前的文字
  const wordRegExp = /^([^\<]+)\<\/[a-z]+[1-6]?\>/

  // 准备两个栈
  let stack1 = [] // 存储匹配到的开始html标签
  let stack2 = []
  let result = null

  while (index < templateString.length - 1) {
    
    
    tailStr = templateString.substring(index)

    if (startTagRegExp.test(tailStr)) {
    
    
      // 匹配开始标签
      const res = tailStr.match(startTagRegExp)
      const startTag = res[1]
      const attrsString = res[2]
      // 开始将标记放入到栈1中
      stack1.push(startTag)
      // 将对象推入数组
      stack2.push({
    
     tag: startTag, children: [], attrs: parseAttribute(attrsString)})
      // 得到attrsString的长度
      const attrsStringLength = attrsString ? attrsString.length : 0
      // 指针移动标签的长度 + 2 + attrsString.length
      // 为什么 +2,因为 <> 也占两个长度
      index += startTag.length + 2 + attrsStringLength
    } else if (endTagRegExp.test(tailStr)) {
    
    
      // 匹配结束标签
      const endTag = tailStr.match(endTagRegExp)[1]
      // 栈1和栈2都需要弹栈
      const pop_tag = stack1.pop()
      const pop_obj = stack2.pop()
      // 此时栈1的栈顶的元素肯定和endTag相同
      if (endTag === pop_tag) {
    
    
        if (stack2.length > 0) {
    
    
          stack2[stack2.length - 1].children.push(pop_obj)
        } else if (stack2.length === 0) {
    
    
          // 匹配到结束标签,且stack2出栈完毕,证明已经遍历结束,那么结果就是stack2最后出栈的那一项
          result = pop_obj
        }
      } else {
    
    
        throw new Error(`${
      
      pop_tag}便签没有封闭!!`)
      }
      // 指针移动标签的长度 + 3,为什么 +3,因为 </> 也占三个长度
      index += endTag.length + 3
    } else if (wordRegExp.test(tailStr)) {
    
    
      // 识别遍历到的这个字符,是不是文字,并且不能全是空字符
      const word = tailStr.match(wordRegExp)[1]
      if (!/^\s+$/.test(word)) {
    
    
        stack2[stack2.length - 1].children.push({
    
    
          text: word,
          type: '3'
        })
      }
      index += word.length
    } else {
    
    
      index++
    }
  }

  return result
}

Ideia:
Defina o ponteiro móvel para determinar se a string restante é um rótulo inicial ou final ou texto e armazene o nome do rótulo e o contêiner em duas pilhas. Quando o rótulo inicial é encontrado, o rótulo é colocado na pilha 1, o o contêiner gerado é colocado na pilha 2 e o texto é preenchido O conteúdo no topo da pilha 2, quando fechado, o conteúdo no topo da pilha 2 será movido para o contêiner anterior no topo da pilha.

import parse from './parse'

const templateString = `
<div>
    <h3 class="box" title="标题" data-id="1">模拟字符串</h3>
    <ul>
        <li>A</li>
        <li>B</li>
        <li>C</li>
    </ul>
</div>
`
console.log('输入的模板字符串', templateString);
const ast = parse(templateString)
console.log('生成的AST\n', ast)

Resumir

O artigo acima é a parte principal da árvore de sintaxe abstrata AST no Vue. A parte mais empolgante são as ideias de solução de problemas de pilha e ponteiro duplo. Espero que depois de ler este artigo, você tenha uma nova compreensão do algoritmo de pilha e ponteiro. .

Acho que você gosta

Origin blog.csdn.net/gaojinbo0531/article/details/129355082
Recomendado
Clasificación