v-for指令的处理
我们还是通过一个简单的HTML模板来看。给定如下HTML模板:
<div id="app"><p v-for="(item, index) in items">{{item}}</p></div>
在解析</p>标签之前,可以得到大致如下的初始AST对象:
{ // 重点看这里,初步解析后,属性先暂存在attrsList里 attrsList: [ {name: "v-for", value: "(item, index) in items", start: 17, end: 47}, {name: ":key", value: "index", start: 48, end: 60} ], attrsMap: {v-for: "(item, index) in items", :key: "index"}, children: [], parent: {type: 1, tag: "div", attrsList: Array(1), attrsMap: {…}, rawAttrsMap: {…}, …}, rawAttrsMap: {}, tag: "p", type: 1 }
接着调用processFor,解析v-for指令,将解析结果添加到AST对象上中:
function processFor (el: ASTElement) { // 解析节点上的v-for指令 let exp // 从attrsList获取v-for的表达式,并从attrsList中移除v-for if ((exp = getAndRemoveAttr(el, 'v-for'))) { // 解析v-for表达式,得到一个对象{alias: "item",for:"items",iterator1: "index"} const res = parseFor(exp) // 合并到AST对象中 if (res) { extend(el, res) } else if (process.env.NODE_ENV !== 'production') { warn( `Invalid v-for expression: ${exp}`, el.rawAttrsMap['v-for'] ) } } }
AST对象变成如下形式:
{ // v-for以属性的方式混入了AST对象 alias: "item", for: "items", iterator1: "index", // 此时,v-for已经从attrsList中移除 attrsList: [ {name: ":key", value: "index", start: 48, end: 60} ], attrsMap: {v-for: "(item, index) in items", :key: "index"}, children: [], parent: {type: 1, tag: "div", attrsList: Array(1), attrsMap: {…}, rawAttrsMap: {…}, …}, rawAttrsMap: {}, tag: "p", type: 1 }
为简单起见,我们在例子中所展示的AST节点对象包含的信息并不完整,只列出对讲解有帮助的重要信息。
v-if指令的处理
我们将上例中的HTML模板,调整如下:
<div id="app"><p v-if="seen">you can see me</p></div>
经过解析,p标签的AST对象大致如下:
{ // 重点看attrsList attrsList: [{name: "v-if", value: "seen", start: 17, end: 28}], attrsMap: {v-if: "seen"}, children: [], parent: {type: 1, tag: "div", attrsList: Array(1), attrsMap: {…}, rawAttrsMap: {…}, …}, rawAttrsMap: {}, tag: "p", type: 1 }
紧接着,vue对调用processIf 对 v-if进一步处理:
function processIf (el) { // 获取v-if的表达式,并从attrsList中删除v-if const exp = getAndRemoveAttr(el, 'v-if') if (exp) { // 为AST添加if和ifConditions属性 el.if = exp addIfCondition(el, { exp: exp, block: el }) } else { // 为AST添加else或elseif if (getAndRemoveAttr(el, 'v-else') != null) { el.else = true } const elseif = getAndRemoveAttr(el, 'v-else-if') if (elseif) { el.elseif = elseif } } }
AST对象变成如下形式:
{ if: "seen", // ifConditions数组,数组中的每一项代表一个条件语句,其中exp表示条件语句的表达式,block是该条件语句所应用的标签的AST对象。 ifConditions: [{exp: "seen", block: {…}}], // 重点看attrsList attrsList: [{name: "v-if", value: "seen", start: 17, end: 28}], attrsMap: {v-if: "seen"}, children: [], parent: {type: 1, tag: "div", attrsList: Array(1), attrsMap: {…}, rawAttrsMap: {…}, …}, rawAttrsMap: {}, tag: "p", type: 1 }
如果在v-if之后还有一个p标签使用了v-else指令,还需要执行processIfConditions将其解析结果push到前面这个有if属性的节点的ifConditions数组中:
function processIfConditions (el, parent) { // 获取v-else的上一个节点 const prev = findPrevElement(parent.children) // 如果上一个节点存在且有if属性,则将v-else的解析结果,push到该节点的ifConditions属性数组 if (prev && prev.if) { addIfCondition(prev, { exp: el.elseif, block: el }) } else if (process.env.NODE_ENV !== 'production') { warn( `v-${el.elseif ? ('else-if="' + el.elseif + '"') : 'else'} ` + `used on element <${el.tag}> without corresponding v-if.`, el.rawAttrsMap[el.elseif ? 'v-else-if' : 'v-else'] ) } }
v-if所在的AST节点变成了以下的形式:
{ if: "seen", // v-if后面的v-else节点会添加到ifConditions数组。 ifConditions: [ {exp: "seen", block: {…}}, {exp: undefined, block: {…}} ], // 重点看attrsList attrsList: [{name: "v-if", value: "seen", start: 17, end: 28}], attrsMap: {v-if: "seen"}, children: [], parent: {type: 1, tag: "div", attrsList: Array(1), attrsMap: {…}, rawAttrsMap: {…}, …}, rawAttrsMap: {}, tag: "p", type: 1 }
v-on指令的处理
将上例中的HTML模板调整如下:
<div id="app"><p @click="show">click me</p></div>
经过解析,p标签的AST对象大致如下:
{ attrsList: [ {name: "@click", value: "show", start: 17, end: 30], attrsMap: {@click: "show"}, children: [], parent: {type: 1, tag: "div", attrsList: Array(1), attrsMap: {…}, rawAttrsMap: {…}, …}, rawAttrsMap: {}, tag: "p", type: 1 }
vue在在下一步的处理中,会将通过v-on所绑定的事件及时间处理函数,添加到AST对象的events属性中:
// ...省略其他代码 if (onRE.test(name)) { // 如果是v-on指令,给一条事件处理器 name = name.replace(onRE, '') isDynamic = dynamicArgRE.test(name) if (isDynamic) { name = name.slice(1, -1) } // 为AST对象添加on属性事件处理器 addHandler(el, name, value, modifiers, false, warn, list[i], isDynamic) } // 添加事件处理器 function addHandler ( el: ASTElement, name: string, value: string, modifiers: ?ASTModifiers, important?: boolean, warn?: ?Function, range?: Range, dynamic?: boolean ) { //...省略其他代码 let events = el.events || (el.events = {}) const newHandler: any = rangeSetItem({ value: value.trim(), dynamic }, range) const handlers = events[name] // 将事件处理器添加到events中 /* istanbul ignore if */ if (Array.isArray(handlers)) { important ? handlers.unshift(newHandler) : handlers.push(newHandler) } else if (handlers) { events[name] = important ? [newHandler, handlers] : [handlers, newHandler] } else { events[name] = newHandler } el.plain = false }
AST对象变成如下形式:
{ attrsList: [ {name: "@click", value: "show", start: 17, end: 30}], attrsMap: {@click: "show"}, children: [], parent: {type: 1, tag: "div", attrsList: Array(1), attrsMap: {…}, rawAttrsMap: {…}, …}, rawAttrsMap: {}, tag: "p", type: 1, // v-on绑定的事件,会在events中添加一条记录 events: {click: {value: "show", dynamic: false, start: 17, end: 30}}, hasBindings: true }
v-model指令的处理
将上例中的HTML模板调整如下:
<div id="app"><input v-model="message" placeholder="edit me"></div>
经过解析,input标签的AST对象大致如下:
{ attrsList: [ {name: "v-model", value: "message", start: 21, end: 38}, {name: "placeholder", value: "edit me", start: 39, end: 60} ], attrsMap: {v-model: "message",placeholder: "edit me"}, children: [], parent: {type: 1, tag: "div", attrsList: Array(1), attrsMap: {…}, rawAttrsMap: {…}, …}, rawAttrsMap: {}, tag: "input", type: 1 }
在下一步的处理中,编译器调用addDirective给AST节点添加directives属性,并将v-model的解析结果添加到directives中。
// 将一条指令对象添加到AST节点的directives属性数组中,el.directives = [{name,rawName,...}] function addDirective ( el: ASTElement, name: string, rawName: string, value: string, arg: ?string, isDynamicArg: boolean, modifiers: ?ASTModifiers, range?: Range ) { (el.directives || (el.directives = [])).push(rangeSetItem({ name, rawName, value, arg, isDynamicArg, modifiers }, range)) el.plain = false }
AST节点变成如下内容:
{ attrsList: [ {name: "placeholder", value: "edit me", start: 39, end: 60}], attrsMap: {v-model: "message",placeholder: "edit me"}, children: [], // 添加directives属性,并在此数组中添加一条记录 directives:[{name: "model", rawName: "v-model", value: "message", arg: null, isDynamicArg: false,modifiers: undefined,start:21,end:38}], parent: {type: 1, tag: "div", attrsList: Array(1), attrsMap: {…}, rawAttrsMap: {…}, …}, rawAttrsMap: {}, tag: "input", type: 1 }