Vue之render函数生成细节
前言 上篇文章分析了Vue中render函数的构建以及调用过程,在整体上梳理了构建出render以及调用的逻辑。知其然也知其所以然,本文就主要分析html -> vnode的过程。
前言
上篇文章分析了Vue中render函数的构建以及调用过程,在整体上梳理了构建出render以及调用的逻辑。知其然也知其所以然,本文就主要分析html -> vnode的过程。
具体分析
首先确定简单实例:
<div id="app">
{{ text }}
</div>
承上启下,上一篇文章中知道构建出render函数的逻辑如下:
其中主要点就是compile和createFunction的处理了:
compile是将template转换为指定结构的对象
createFunction是根据compile创建的render函数主体结构生成函数,即可执行的render函数
compile具体处理
阅读源码逻辑可得,实际上compile的主要处理点是:
var compiled = baseCompile(template, finalOptions);
即调用baseCompile函数,而baseCompile则是核心,这边主要代码如下:
var ast = parse(template.trim(), options);
var code = generate(ast, options);
return {
ast: ast,
render: code.render
};
从上面代码可知baseCompile主要处理是:
- 将template装换为ast树结构对象
- 根据ast树结构构建render函数主体部分
parse解析template
parse部分非常复杂,这里也只会分析主要脉络,例如解析html、解析属性、解析文本主要点,例如指令、注释等暂时不具体分析。
现在具体看看parse主要的处理逻辑,如下图:
实际上parse中会调用parseHTML也具体解析,其中parseHTML在new Vue实例化时的主要处理点如下:
function parseHTML(html, options) {
// 用于存储标签对象
var stack = [];
// tokens解析的位置,即
var index = 0;
while(html) {
// 其他处理
// 判断是否以<开头,即标签开始
var textEnd = html.indexOf('<');
if (textEnd === 0) {
// parseStartTag解析出开始标签
var startTagMatch = parseStartTag();
if (startTagMatch) {
// 处理开始标签
handleStartTag(startTagMatch);
if (shouldIgnoreFirstNewline(lastTag, html)) {
advance(1);
}
continue
}
}
if(textEnd >= 0) {
// 处理文本
// 主要处理
options.chars(text)
}
}
}
从parseHTML涉及处理实际上会调用parseStartTag和handleStartTag来解析出开始标签,具体的处理如下:
// 正则匹配开始标签
var start = html.match(startTagOpen);
if (start) {
var match = {
tagName: start[1],
attrs: [],
start: index
};
advance(start[0].length);
// 收集该标签的属性
}
通过parseStartTag函数会得到:
- 标签名,即tagName
- 该标签的属性集合,即attrs
上面中还有一个重要的函数,即advance,该函数是处理index和html值,这两个值分别表示:
- index:当前解析开始位置
- html:解析源
实际上parseStartTag会将html中开始标签和属性字符串截取掉,index的位置也会改变,例如:
“<div id=“app”> {{ text }} </div>” 经过parseStartTag会将其截取为 {{ text }} </div>
即截取了开始标签部分
handleStartTag处理开始标签和属性
通过parseStartTag获取了开始标签和其所有属性之后,接下来就是handleStartTag函数的调用,通过其源码分析,主要的处理点如下:
attrs[i] = {
name: args[1],
value: decodeAttr(value, shouldDecodeNewlines)
};
stack.push({
tag: tagName,
lowerCasedTag: tagName.toLowerCase(),
attrs: attrs
});
options.start(tagName, ...);
从上面的代码中可以看出,实际上是主要处理属性,并构建结构对象存入stack中。
构建了标签和属性结构对象,之后调用options中的start,实际上start主要的处理点可看成两点(这里只关心这两点):
createASTElement函数调用
将创建的astElement存入stack中
// createASTElement函数主要处理
return {
type: 1,
tag: tag,
attrsList: attrs,
attrsMap: makeAttrsMap(attrs),
parent: parent,
children: []
}
当createASTElement创建结果之后存入stack中,一次循环就结束了。
此时会进行下一次循环,而之前advance将html中存储的字符串截取了。
实例:
<div id=“app”> {{text}} </div> 则会变成{{text}} </div>,即此时html的值
那下一轮循环就是处理{{text}}文本相关的,实际上会调用options的chars函数,该函数的主要处理逻辑如下:
var children = currentParent.children;
var res = parseText(text);
children.push({
type: 2,
expression: res.expression,
tokens: res.tokens,
text: text
})
parseText处理文本
处理类似{{text}}文本节点,实际上就是同parseText来实现的,而该函数实际上处理文本主要如下:
var tokens = [];
var rawTokens = [];
tokens.push(("_s(" + exp + ")"));
rawTokens.push({ '@binding': exp });
return {
expression: tokens.join('+'),
tokens: rawTokens
}
实际上会将使用到响应式数据的文本变成:_s(text),并且设置了@binding属性表示引用了哪个响应式变量。
还有一点就是对于换行的处理也会保留,即文本区域原样,该有换行就有换行只是响应式变量的特殊处理而已。
例如:
<div>
{{text}}
</div>
实际上text前后都有换行,此时expression就是 “\n + _s(text) + \n”,tokens中也是
至于结束标签的处理,其中没有本次需要关注点,就略过。
parse总结
template完整值是:
<div id=“app”> {{ text }} </div>
经过parseHTML + parseText的解析,得到如下的主要ast结构:
type: 1,
tag: 'div',
attrs: [{ name: 'id', value: 'app'}],
attrList: [{name: 'id', value: 'app'}],
attrMap: {id: 'app'}
children: [
expression: ""\n "+_s(text)+"\n ""
text: "↵ {{ text }}↵ "
tokens: (3) ["↵ ", {…}, "↵ "]
type: 2
]
generate是如何构建render的
通过parse获得了指定ast结构对象,接下来就通过generate来构建render主体部分,这部分也比较复杂,这里就概括性的梳理下:
实际上generate是构建成类似Vue官方提供的jsx渲染的结构
实例的ast结构实际上构建成了:
with(this) {
return _c('div',
{
attrs:{"id":"app"}
},
[
_v("\n "+_s(text)+"\n ")
]
)
}
上面我是使用函数形式表示的,实际上Vue源码此处是字符串,即:
“with(this){return _c(‘div’,{attrs:{“id”:“app”}},[_v(”\n “+_s(text)+”\n “)])}”
解释下_c和_v是什么,在上篇文章中,我提及到了_c:
_c就是$createElement,即h函数
-v、_s是指什么呢?
-v实际上是createTextVNode,即创建虚拟文本节点
-s实际上是toString,处理对象和其他形式文本输出
通过parse和generate得到了上面的函数体,那么接下来就调用createFunction函数了。
createFunction函数
该函数实际上就是创建函数,如何创建?调用Function构造函数:
new Function(code);
至此,我们得到了render函数:
var render = new Function("with(this) { return _c('div', attrs: {'id': 'app'}, [_v('\n ' + _s(text) + ' \n')])}");
总结
通过上面主要分析了处理html、处理文本情况,对于指令相关等部分直接略过,满足了分析这边的目的:探寻下Vue是如何解析template的。
整个处理流程如下:
构建code:
$mount -> compileToFunctions -> compile -> baseCompile -> parse -> generate
其中parse中主要的处理:
parse -> parseHTML (循环处理startTag、text等)
构建render函数:
parse得到ast结构 -> generate -> render
而vnode的创建实际上是在调用阶段发生的,即执行render函数,函数体重_c、_v等函数触发的VNode创建。
这部分会在下篇文章中重点分析。
更多推荐
所有评论(0)