vue2源码-五、将模板编译解析成AST语法树2

发布时间 2023-04-15 21:06:03作者: 楸枰~

生成render函数

  1. 前言

    • 上篇,生成ast语法树,而这篇使用ast语法树生成render函数。
    export function compileToFunction(template) {
      // 1,将模板编译称为 AST 语法树
      let ast = parserHTML(template);
      // 2,使用 AST 生成 render 函数
      let code = codegen(ast);
    }
    
  2. 生成render函数

    codegen方法:根据ast语法树生成render函数。即传入ast语法树,返回render函数。

    function codegen(ast) {
      // 儿子,后面会说
      let children = genChildren(ast.children)
      // 字符串拼接render函数
      let code = `_c('${ast.tag}',${
        ast.attrs.length > 0 ? genProps(ast.attrs) : 'null'
      }${ast.children.length ? `,${children}` : ''})`
      // 返回render函数
      return code
    }
    

    在里面其中有genChildren方法和genProps方法。将分别介绍这两个方法

  3. 处理属性:genProps(ast.attrs)方法

    说明:格式化属性信息。

    // 将属性(attrs)数组格式化为对象
    function genProps(attrs) {
      let str = '' // {name,value}
      for (let i = 0; i < attrs.length; i++) {
        let attr = attrs[i]
        // 如果是style属性
        if (attr.name === 'style') {
          let obj = {}
          // 进行处理
          attr.value.split(';').forEach((item) => {
            let [key, value] = item.split(':')
            obj[key] = value
          })
          attr.value = obj
        }
         // 如果不是,使用JSON.stringify将value转换为字符串
        str += `${attr.name}:${JSON.stringify(attr.value)},`
      }
      // 去掉最后的多余的逗号,并且用{}包裹
      return `{${str.slice(0, -1)}}`
    }
    // _c('div',{id:"app",a:"1",b:"2",style:{"color":" red"}},
    
  4. 处理儿子:genChildren(ast.children)方法

    实现就是进行循环递归:

    function genChildren(children) {
      // 递归孩子
      return children.map((child) => gen(child)).join(',')
    }
    

    其中有一个gen方法,这个方法就是处理儿子的

    直接上代码:

    // 匹配{{xxx}}
    const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g
    function gen(node) {
      // 如果是元素继续递归就好
      if (node.type === 1) {
        return codegen(node)
      } else {
        // 如果不是就是文本类型,对文本进行处理
        let text = node.text
        // 匹配不上,即使普通文本
        if (!defaultTagRE.test(text)) {
          // 包装为_v
          return `_v(${JSON.stringify(text)})`
        } else {
           // 对表达式和普通值执行拼接操作 
          // <div>aaa{{xxx}}</div>
          let tokens = []
          let match
          defaultTagRE.lastIndex = 0
          let lastIndex = 0
          // 执行循环获得结果
           while ((match = defaultTagRE.exec(text))) {
            // 获取当前捕获到的位置
            let index = match.index
            // 说明匹配到了内容,将前一段'<div>aaa '中的 aaa 放入 tokens 数组中
            if (index > lastIndex) {
              tokens.push(JSON.stringify(text.slice(lastIndex, index)))
            }
           // match[1]:表示花括号中间的内容 name,需要处理表达式部分可能存在的换行或回车
            tokens.push(`_s(${match[1].trim()})`)
            // 更新lastIndex,用于下次比对
            lastIndex = index + match[0].length
          }
          // 如果还有剩余的部分要处理
          if (lastIndex < text.length) {
            tokens.push(text.slice(lastIndex))  // 从lastIndex到最后
          }
            // 返回
          return `_v(${tokens.join('+')})`
        }
      }
    }