Vue源码解析:模版编译之来龙去脉(三)
本篇主要讲解,vue文本的解析。话不多说,先上源码://源码位于 src/compiler/parser/text-parsre.jsconst defaultTagRE = /\{\{((?:.|\n)+?)\}\}/gconst buildRegex = cached(delimiters => {const open = delimiters[0].replace(regexEscap
本篇主要讲解,vue文本的解析。话不多说,先上源码:
// 源码位于 src/compiler/parser/text-parsre.js
const defaultTagRE = /\{\{((?:.|\n)+?)\}\}/g
const buildRegex = cached(delimiters => {
const open = delimiters[0].replace(regexEscapeRE, '\\$&')
const close = delimiters[1].replace(regexEscapeRE, '\\$&')
return new RegExp(open + '((?:.|\\n)+?)' + close, 'g')
})
export function parseText (text,delimiters) {
const tagRE = delimiters ? buildRegex(delimiters) : defaultTagRE
if (!tagRE.test(text)) {
return
}
const tokens = []
const rawTokens = []
/**
* let lastIndex = tagRE.lastIndex = 0
* 上面这行代码等同于下面这两行代码:
* tagRE.lastIndex = 0
* let lastIndex = tagRE.lastIndex
*/
let lastIndex = tagRE.lastIndex = 0
let match, index, tokenValue
while ((match = tagRE.exec(text))) {
index = match.index
// push text token
if (index > lastIndex) {
// 先把'{{'前面的文本放入tokens中
rawTokens.push(tokenValue = text.slice(lastIndex, index))
tokens.push(JSON.stringify(tokenValue))
}
// tag token
// 取出'{{ }}'中间的变量exp
const exp = parseFilters(match[1].trim())
// 把变量exp改成_s(exp)形式也放入tokens中
tokens.push(`_s(${exp})`)
rawTokens.push({ '@binding': exp })
// 设置lastIndex 以保证下一轮循环时,只从'}}'后面再开始匹配正则
lastIndex = index + match[0].length
}
// 当剩下的text不再被正则匹配上时,表示所有变量已经处理完毕
// 此时如果lastIndex < text.length,表示在最后一个变量后面还有文本
// 最后将后面的文本再加入到tokens中
if (lastIndex < text.length) {
rawTokens.push(tokenValue = text.slice(lastIndex))
tokens.push(JSON.stringify(tokenValue))
}
// 最后把数组tokens中的所有元素用'+'拼接起来
return {
expression: tokens.join('+'),
tokens: rawTokens
}
}
创建来tagRE变量用来检查变量是动态还是文本,其原理主要是用来检测是否有{{}},从而判断是否是动态文本。如果包不包含就直接return,如果包含就继续往下创建变量。开启while循环,首先取得字符串中第一个变量在字符串中的起始位置赋给index
,然后比较index
和lastIndex
的大小,此时你可能有疑问了,这个lastIndex
是什么呢?在上面定义变量中,定义了let lastIndex = tagRE.lastIndex = 0
,所以lastIndex
就是tagRE.lastIndex
,而tagRE.lastIndex
又是什么呢?当调用exec( )
的正则表达式对象具有修饰符g
时,它将把当前正则表达式对象的lastIndex
属性设置为紧挨着匹配子串的字符位置,当同一个正则表达式第二次调用exec( )
,它会将从lastIndex
属性所指示的字符串处开始检索,如果exec( )
没有发现任何匹配结果,它会将lastIndex
重置为0。index>lastIndex
时,表示变量前面有纯文本,那么就把这段纯文本截取出来,存入rawTokens
中,同时再调用JSON.stringify
给这段文本包裹上双引号,存入tokens
中。
如果index
不大于lastIndex
,那说明index
也为0,即该文本一开始就是变量,例如:hello
。那么此时变量前面没有纯文本,那就不用截取,直接取出匹配结果的第一个元素变量名,将其用_s()
包裹存入tokens
中,同时再把变量名构造成{'@binding': exp}
存入rawTokens
中。
当while
循环完毕时,表明文本中所有变量已经被解析完毕,如果此时lastIndex < text.length
,那就说明最后一个变量的后面还有纯文本,那就将其再存入tokens
和rawTokens
中。
这就是文本解析的原理。
vue也同样做了优化,就是vue区分了静态节点,和动态节点。这样未来在patch的过程中就可以直接跳过静态节点。
所有的节点都解析完毕之后,生成了AST树之后,是如何生成render函数的呢?
假如,我的template的模版是这样的:
<div id="NLRX"><p>Hello {{name}}</p></div>
那么模版编译后的结果应该是:
ast = {
'type': 1,
'tag': 'div',
'attrsList': [
{
'name':'id',
'value':'NLRX',
}
],
'attrsMap': {
'id': 'NLRX',
},
'static':false,
'parent': undefined,
'plain': false,
'children': [{
'type': 1,
'tag': 'p',
'plain': false,
'static':false,
'children': [
{
'type': 2,
'expression': '"Hello "+_s(name)',
'text': 'Hello {{name}}',
'static':false,
}
]
}]
}
是的,就是这个鬼样子。
所以,我们需要对这个AST树进行递归遍历。
export function generate (ast,option) {
const state = new CodegenState(options)
const code = ast ? genElement(ast, state) : '_c("div")'
return {
render: `with(this){return ${code}}`,
staticRenderFns: state.staticRenderFns
}
}
调用generate
函数并传入优化后得到的ast。generate内部会判断如果ast不为空,那么就调用
genElement创建虚拟dom节点。
export function genElement (el: ASTElement, state: CodegenState): string {
if (el.staticRoot && !el.staticProcessed) {
return genStatic(el, state)
} else if (el.once && !el.onceProcessed) {
return genOnce(el, state)
} else if (el.for && !el.forProcessed) {
return genFor(el, state)
} else if (el.if && !el.ifProcessed) {
return genIf(el, state)
} else if (el.tag === 'template' && !el.slotTarget) {
return genChildren(el, state) || 'void 0'
} else if (el.tag === 'slot') {
return genSlot(el, state)
} else {
// component or element
let code
if (el.component) {
code = genComponent(el.component, el, state)
} else {
const data = el.plain ? undefined : genData(el, state)
const children = el.inlineTemplate ? null : genChildren(el, state, true)
code = `_c('${el.tag}'${
data ? `,${data}` : '' // data
}${
children ? `,${children}` : '' // children
})`
}
// module transforms
for (let i = 0; i < state.transforms.length; i++) {
code = state.transforms[i](el, code)
}
return code
}
}
通过以上的方法,生成不同类型的,文本节点,标签节点,注释节点。从而生成了render函数,这样在vue挂载阶段,就可以生成vNode.
以上就是vue模版解析的大致过程。初略分析,还需大师指点!!
更多推荐
所有评论(0)