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中提供的方法,它的作用是创建虚拟节点, 接受三个参数, 分别是:
- 标签名
- 一个包含模板相关属性的数据对象
- 子节点列表
渲染函数可以生产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 生成字符串,最先生成 根节点, 然后在 子节点字符串生成后, 将其拼接到 根节点的参数中, 子节点 的子节点 拼接在 子节点的参数中, 一层一层的拼接,知道拼接成完整的字符串。
推荐阅读
- 急于表达——往往欲速则不达
- CVE-2020-16898|CVE-2020-16898 TCP/IP远程代码执行漏洞
- 布丽吉特,人生绝对的赢家
- 第三节|第三节 快乐和幸福(12)
- Y房东的后半生14
- 赢在人生六项精进二阶Day3复盘
- 学无止境,人生还很长
- 我错了,余生不再打扰
- 牛人进化+|牛人进化+ 按自己的意愿过一生
- 别怪生活,自己作的!