vue2-代码生成器

在vue2-AST抽象语法树 https://www.jianshu.com/p/d7822b42a3e3
中 说了一下 把模板 --> ast抽象语法树 --> 渲染函数;
其实在里面 详细 是 模板 ——> 模板编译 ——> 渲染函数
其中 模板编译 包括 三个模块: 解析器 -> 优化器 -> 代码生成器

  • 解析器: 把模板解析成AST https://www.jianshu.com/p/83568cff95e1
  • 优化器:遍历AST 标记静态节点
  • 代码生成器: 使用AST 生成渲染函数
现在开始代码生成器
代码生成器的作用是将AST转换成渲染函数中的内容,这个内容可以称为代码字符串。
代码字符串可以被包装再函数中执行,这个函数就是我们说的渲染函数。
比如:
Hello {{name}}
转换为AST并被优化器优化之后
{ 'type': 1, 'tag': 'div', 'attrsList': [ { 'name': 'id', 'value': 'el' } ], 'attsMap': { 'id': 'el' }, 'children': [ { 'type': 2, 'expression': '"Hello "+_s(name)', 'text': 'Hello {{name}}', 'static': false// 静态节点标记 } ], 'plain': false, // true 则没有属性 'attrs': [ { 'name': 'id', 'value': '"el"' } ], 'static': false, // 静态节点标记 'staticRoot': false // 静态根节点标记 }

代码生成器可以把这个AST转换为字符串:
`with (this) { return _c( 'div', { attrs: {'id': 'el'}, }, [ _v('Hello '+ _s(name)) ] ) }`

其中_s 是下面toString函数的别名
function toString(val) { return val === null ? '' : typeof val === 'object' ? JSON.stringify(val, null, 2) :// 返回值文本在每个级别缩进2个的空格 String(val) }

代码字符串中 _c其实是 createElement的别名, createElement是 虚拟DOM中提供的方法,它的作用是创建虚拟节点, 接受三个参数, 分别是:
  • 标签名
  • 一个包含模板相关属性的数据对象
  • 子节点列表
调用createElement 方法,我们可以得到一个VNode.
渲染函数可以生产VNode 的原因是: 渲染函数执行了createElement方法创建了一个VNode.
节点有三种类型 分别对应三种不同的创建方法和别名
类型 创建方法 别名
元素节点 createElement _c
文本节点 createTextVNode _v
注释节点 createEmptyVNode _e
代码生成器的原理 元素节点
生成元素节点 其实就是生成一个_c的函数调用字符串
function genElement(el, state){ // 如果 el.plain是true, 则说明 节点没有属性 const data = https://www.it610.com/article/el.plain ? undefined: genData(el, state)const children = genChildren(el, state)code = `_c('${el.tag}'${ data ? `, ${data}` : '' // data }${ children ? `,${children}`: '' //children })` return code }

代码中el的plain 属性是编译时发现的, 如果节点没有属性,那么plain设置为true,我们可以通过plain 来判断 是否需要获取节点的属性属性。
其中 genData 和 genChildren 分别获取 data和children。最后拼接好
“_c(tagName, data, children)”
function genData(el, state){ let data = 'https://www.it610.com/article/{' // key if(el.key){ data += `key:${el.key},` } // ref if(el.ref){ data += `ref:${el.ref},` } // pre if(el.pre){ data += `pre:${el.pre},` } // ...还有其他很多这种情况 data = https://www.it610.com/article/data.replace(/,$/,'') + '}' return data }

genData 其实也是拼接字符串 ,发现节点存在哪些属性数据,就拼接到data中,最后返回
genChildren的 逻辑也是拼接字符串,通过循环子节点列表,根据不同 的子节点类型 生成不同的 节点字符串 将其 拼接到一起
function genChildren(el, state){ const children = el.children if(children.length){ return `[${children.map( c => genNode(c, state).join(','))}]` } function genNode(node, state){ // 元素 if(node.type === 1){ return genElement(node, state) } // 注释节点 if(node.type === 3 && node.isComment){ return genComment(node) } // 文本节点 else { return genText(node) } } }

文本节点
生成文本节点很简单, 把文本放到_v函数的参数中即可
function genText(node){ return `_v(${node.type === 2 ? node.expression : JSON.stringify(node.text) })` }

如果是动态文本比如 my name is {{name}}, 则使用expression.如果是静态文本,这使用text;
这里text 使用JSON.stringify方法 的原因是:
expression 的文本是这样的 ' "Hello " + _s(name) '
而 text 的文本 是 "Hello zs"
我们希望 静态文本是 ' "Hello zs" '
所以这里用个JSON.stringify给文本包装一层字符串。
注释节点
function genComment(node){ return `_e(${JSON.stringify(node.text)})` }

注释节点与文本节点相同, 只需要把文本放在_e的参数中 即可。
【vue2-代码生成器】代码生成器 其实就是 字符串拼接 的过程。 通过递归AST 生成字符串,最先生成 根节点, 然后在 子节点字符串生成后, 将其拼接到 根节点的参数中, 子节点 的子节点 拼接在 子节点的参数中, 一层一层的拼接,知道拼接成完整的字符串。

    推荐阅读