getAndRemoveAttr从ast模板对象中取出相应的属性。检测属性是否存在,通过对象attrsMap来检测,提升效率如果存在,则从attrsList中中移除如果第三个传参为true,删除attrsMap中对应的属性返回取到的结果,或者undefined// note: this only removes the attr from the Array (attrsList) so that it// doesn’t get processed by processAttrs.// By default it does NOT remove it from the map (attrsMap) because the map is// needed during codegen.export function getAndRemoveAttr ( el: ASTElement, name: string, removeFromMap?: boolean): ?string { let val if ((val = el.attrsMap[name]) != null) { const list = el.attrsList for (let i = 0, l = list.length; i < l; i++) { if (list[i].name === name) { list.splice(i, 1) break } } } if (removeFromMap) { delete el.attrsMap[name] } return val} getBindingAttr获取动态属性值拼接属性名:|v-bind,调用getAndRemoveAttr读取相应的属性值如果获取到相应的属性值,则调用parseFilters解析返回值中可能存在的过滤器,并返回如果第三个传参不为false,返回相应的静态属性,并将静态属性格式化为字符串"“demo1"",返回否则,无返回/** 第三个参数传true会在获取不到动态属性的时候取静态属性 /export function getBindingAttr ( el: ASTElement, name: string, getStatic?: boolean): ?string { const dynamicValue = getAndRemoveAttr(el, ‘:’ + name) || getAndRemoveAttr(el, ‘v-bind:’ + name) if (dynamicValue != null) { // 如果存在过滤器,将匹配到的字符串使用过滤器包裹。 return parseFilters(dynamicValue) } else if (getStatic !== false) { const staticValue = getAndRemoveAttr(el, name) if (staticValue != null) { return JSON.stringify(staticValue) } }} parseFilters{{input | filter1 | filter2 }} 解析为:_f(“filter2”)(_f(“filter1”)(input))/ @flow //* 匹配任意非空字符,),.,+,-,,$,] * 排除一些其他用法产生的/,诸如a++ / b, a– / b, a/b, (a + a1) / b, ../path*/const validDivisionCharRE = /[\w).+-$]]//** 解析出filter的条件是:匹配到|, * 并且|不在单引号,双引号,模板引用符,正则,括号,中括号,大括号中, * 并且不是|| /export function parseFilters (exp: string): string { let inSingle = false let inDouble = false let inTemplateString = false let inRegex = false let curly = 0 let square = 0 let paren = 0 let lastFilterIndex = 0 let c, prev, i, expression, filters for (i = 0; i < exp.length; i++) { prev = c c = exp.charCodeAt(i) // 0x5C => \ if (inSingle) { if (c === 0x27 && prev !== 0x5C) inSingle = false } else if (inDouble) { if (c === 0x22 && prev !== 0x5C) inDouble = false } else if (inTemplateString) { if (c === 0x60 && prev !== 0x5C) inTemplateString = false } else if (inRegex) { if (c === 0x2f && prev !== 0x5C) inRegex = false } else if ( c === 0x7C && // | exp.charCodeAt(i + 1) !== 0x7C && exp.charCodeAt(i - 1) !== 0x7C && !curly && !square && !paren ) { if (expression === undefined) { // first filter, end of expression lastFilterIndex = i + 1 expression = exp.slice(0, i).trim() } else { pushFilter() } } else { switch (c) { case 0x22: inDouble = true; break // " case 0x27: inSingle = true; break // ’ case 0x60: inTemplateString = true; break // case 0x28: paren++; break // ( case 0x29: paren--; break // ) case 0x5B: square++; break // [ case 0x5D: square--; break // ] case 0x7B: curly++; break // { case 0x7D: curly--; break // } } if (c === 0x2f) { // / let j = i - 1 let p // find first non-whitespace prev char for (; j >= 0; j--) { p = exp.charAt(j) if (p !== ' ') break } if (!p || !validDivisionCharRE.test(p)) { inRegex = true } } } } if (expression === undefined) { expression = exp.slice(0, i).trim() } else if (lastFilterIndex !== 0) { pushFilter() } function pushFilter () { (filters || (filters = [])).push(exp.slice(lastFilterIndex, i).trim()) lastFilterIndex = i + 1 } if (filters) { for (i = 0; i < filters.length; i++) { expression = wrapFilter(expression, filters[i]) } } return expression}function wrapFilter (exp: string, filter: string): string { const i = filter.indexOf('(') if (i < 0) { // _f: resolveFilter return
_f("${filter}”)(${exp}) } else { const name = filter.slice(0, i) const args = filter.slice(i + 1) return
_f("${name}")(${exp}${args !== ‘)’ ? ‘,’ + args : args} }} preTransformNode/* @flow *//** * Expand input[v-model] with dyanmic type bindings into v-if-else chains * Turn this: * <input v-model="data[type]" :type="type"> * into this: * <input v-if="type === 'checkbox'" type="checkbox" v-model="data[type]"> * <input v-else-if="type === 'radio'" type="radio" v-model="data[type]"> * <input v-else :type="type" v-model="data[type]"> */import { addRawAttr, getBindingAttr, getAndRemoveAttr} from 'compiler/helpers'import { processFor, processElement, addIfCondition, createASTElement} from 'compiler/parser/index'/** 处理input标签的v-model */function preTransformNode (el: ASTElement, options: CompilerOptions) { if (el.tag === 'input') { const map = el.attrsMap if (!map['v-model']) { return } let typeBinding if (map[':type'] || map['v-bind:type']) { typeBinding = getBindingAttr(el, 'type') } if (!map.type && !typeBinding && map['v-bind']) { typeBinding =
(${map[‘v-bind’]}).type } if (typeBinding) { const ifCondition = getAndRemoveAttr(el, 'v-if', true) const ifConditionExtra = ifCondition ?
&&(${ifCondition}): `` const hasElse = getAndRemoveAttr(el, 'v-else', true) != null const elseIfCondition = getAndRemoveAttr(el, 'v-else-if', true) // 1. checkbox const branch0 = cloneASTElement(el) // process for on the main node,如果有v-for,将v-for解析为for,alias等属性,并添加到branch0上 processFor(branch0) // ast直接添加type属性 addRawAttr(branch0, 'type', 'checkbox') processElement(branch0, options) branch0.processed = true // prevent it from double-processed branch0.if =
(${typeBinding})===‘checkbox’+ ifConditionExtra addIfCondition(branch0, { exp: branch0.if, block: branch0 }) // 2. add radio else-if condition const branch1 = cloneASTElement(el) getAndRemoveAttr(branch1, 'v-for', true) addRawAttr(branch1, 'type', 'radio') processElement(branch1, options) addIfCondition(branch0, { exp:
(${typeBinding})===‘radio’+ ifConditionExtra, block: branch1 }) // 3. other const branch2 = cloneASTElement(el) getAndRemoveAttr(branch2, 'v-for', true) addRawAttr(branch2, ':type', typeBinding) processElement(branch2, options) addIfCondition(branch0, { exp: ifCondition, block: branch2 }) if (hasElse) { branch0.else = true } else if (elseIfCondition) { branch0.elseif = elseIfCondition } return branch0 } }}function cloneASTElement (el) { return createASTElement(el.tag, el.attrsList.slice(), el.parent)}export default { preTransformNode}processForextend(el, res),el.for, el.alias, el.iterator1, el.iterator2export function processFor (el: ASTElement) { let exp if ((exp = getAndRemoveAttr(el, 'v-for'))) { const res = parseFor(exp) if (res) { extend(el, res) } else if (process.env.NODE_ENV !== 'production') { warn(
Invalid v-for expression: ${exp} ) } }} parseFor这个函数解析v-for字符串,并返回,比如 item in items返回{ for: items, alias: item }; (item, index) in items返回{ for: items, alias: item, iterator1: index }; (item, key, index) in items返回{ for: items, alias: item, iterator1: key, iterator2: index };/** * item in items *?最小贪婪匹配,如 a in b in c 则匹配 * a 而不是 a in b, in item 则匹配 '' */export const forAliasRE = /([^]*?)\s+(?:in|of)\s+([^]*)/ /** * 匹配多个参数。 */export const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/const stripParensRE = /^\(|\)$/g/** 解析v-for表达式,并返回 */export function parseFor (exp: string): ?ForParseResult { const inMatch = exp.match(forAliasRE) if (!inMatch) return const res = {} res.for = inMatch[2].trim() const alias = inMatch[1].trim().replace(stripParensRE, '') const iteratorMatch = alias.match(forIteratorRE) if (iteratorMatch) { res.alias = alias.replace(forIteratorRE, '') res.iterator1 = iteratorMatch[1].trim() if (iteratorMatch[2]) { res.iterator2 = iteratorMatch[2].trim() } } else { res.alias = alias } return res} processElementexport function processElement (element: ASTElement, options: CompilerOptions) { processKey(element) // determine whether this is a plain element after // removing structural attributes element.plain = !element.key && !element.attrsList.length processRef(element) processSlot(element) processComponent(element) /** * 赋值 el.staticClass, classBinding, staticStyle, styleBinding */ for (let i = 0; i < transforms.length; i++) { element = transforms[i](element, options) || element } /** 处理attrsList 剩余属性 */ processAttrs(element)} processSlot如果el.tag === slot,获取el的name并赋值给slotname属性不是<slot>,获取slot-scope,并给元素赋值slotScope属性获取元素的动态slot,如果不是template且slotScope属性不存在,则给el的attrs数组属性增加{name: 'slot', value: slotTarget}function processSlot (el) { if (el.tag === 'slot') { el.slotName = getBindingAttr(el, 'name') if (process.env.NODE_ENV !== 'production' && el.key) { warn(
`key` does not work on <slot> because slots are abstract outlets +
and can possibly expand into multiple elements. +
Use the key on a wrapping element instead. ) } } else { let slotScope if (el.tag === 'template') { slotScope = getAndRemoveAttr(el, 'scope') /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && slotScope) { warn(
the “scope” attribute for scoped slots have been deprecated and +
replaced by “slot-scope” since 2.5. The new “slot-scope” attribute +
can also be used on plain elements in addition to <template> to +
denote scoped slots., true ) } el.slotScope = slotScope || getAndRemoveAttr(el, 'slot-scope') } else if ((slotScope = getAndRemoveAttr(el, 'slot-scope'))) { /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && el.attrsMap['v-for']) { warn(
Ambiguous combined usage of slot-scope and v-for on <${el.tag}> +
(v-for takes higher priority). Use a wrapper <template> for the +
scoped slot to make it clearer., true ) } el.slotScope = slotScope } const slotTarget = getBindingAttr(el, 'slot') if (slotTarget) { el.slotTarget = slotTarget === '""' ? '"default"' : slotTarget // preserve slot as an attribute for native shadow DOM compat // only for non-scoped slots. if (el.tag !== 'template' && !el.slotScope) { addAttr(el, 'slot', slotTarget) } } }} processComponentfunction processComponent (el) { let binding if ((binding = getBindingAttr(el, 'is'))) { el.component = binding } if (getAndRemoveAttr(el, 'inline-template') != null) { el.inlineTemplate = true }} processAttrs对于动态绑定属性,首先截取修饰符,并根据修饰符修饰name,增加事件等,再根据属性名来判断是绑定props还是attrs,需要动态更新的绑定props。对于普通属性,直接增加attr,muted除外,因为这个属性如果使用attr无法触发更新function processAttrs (el) { const list = el.attrsList let i, l, name, rawName, value, modifiers, isProp for (i = 0, l = list.length; i < l; i++) { name = rawName = list[i].name value = list[i].value if (dirRE.test(name)) { // mark element as dynamic el.hasBindings = true // modifiers :input.number modifiers = parseModifiers(name) if (modifiers) { name = name.replace(modifierRE, '') // :input } if (bindRE.test(name)) { // v-bind name = name.replace(bindRE, '') // input value = parseFilters(value) // _f('filter1')(value) isProp = false if (modifiers) { if (modifiers.prop) { isProp = true name = camelize(name) if (name === 'innerHtml') name = 'innerHTML' } if (modifiers.camel) { name = camelize(name) } // 双向绑定,通过emit事件来触发 if (modifiers.sync) { /** 在el的event或者nativeevent中添加事件 * el.event.value = {value: value=$event} || [...] */ addHandler( el,
update:${camelize(name)}, genAssignmentCode(value,
$event) // 'value=$event' ) } } if (isProp || ( !el.component && platformMustUseProp(el.tag, el.attrsMap.type, name) )) { addProp(el, name, value) } else { addAttr(el, name, value) } } else if (onRE.test(name)) { // v-on name = name.replace(onRE, '') addHandler(el, name, value, modifiers, false, warn) } else { // normal directives name = name.replace(dirRE, '') // parse arg const argMatch = name.match(argRE) const arg = argMatch && argMatch[1] if (arg) { name = name.slice(0, -(arg.length + 1)) } addDirective(el, name, rawName, value, arg, modifiers) if (process.env.NODE_ENV !== 'production' && name === 'model') { checkForAliasModel(el, value) } } } else { // literal attribute if (process.env.NODE_ENV !== 'production') { const res = parseText(value, delimiters) if (res) { warn(
${name}="${value}": + 'Interpolation inside attributes has been removed. ' + 'Use v-bind or the colon shorthand instead. For example, ' + 'instead of <div id="{{ val }}">, use <div :id="val">.' ) } } addAttr(el, name, JSON.stringify(value)) // #6887 firefox doesn't update muted state if set via attribute // even immediately after element creation if (!el.component && name === 'muted' && platformMustUseProp(el.tag, el.attrsMap.type, name)) { addProp(el, name, 'true') } } }} parseModel/** * Parse a v-model expression into a base path and a final key segment. * Handles both dot-path and possible square brackets. * * Possible cases: * * - test {exp: "test", key: null} * - test[key] {exp: "test", key: "key"} * - test[test1[key]] {exp: "test", key: "test1[key]"} * - test["a"][key] {exp: "test["a"]", key: "key"} * - xxx.test[a[a].test1[key]] {exp: "xxx.test", key: "a[a].test1[key]"} * - test.xxx.a["asa"][test1[key]] {exp: "test.xxx.a["asa"]", key: "test1[key]"} * */let len, str, chr, index, expressionPos, expressionEndPostype ModelParseResult = { exp: string, key: string | null}/** 获取最靠后的一个完整项为key */ export function parseModel (val: string): ModelParseResult { // Fix https://github.com/vuejs/vue/pull/7730 // allow v-model="obj.val " (trailing whitespace) val = val.trim() len = val.length if (val.indexOf('[') < 0 || val.lastIndexOf(']') < len - 1) { index = val.lastIndexOf('.') if (index > -1) { return { exp: val.slice(0, index), key: '"' + val.slice(index + 1) + '"' } } else { return { exp: val, key: null } } } str = val index = expressionPos = expressionEndPos = 0 while (!eof()) { chr = next() /* istanbul ignore if */ if (isStringStart(chr)) { parseString(chr) } else if (chr === 0x5B) { // [ parseBracket(chr) } } return { exp: val.slice(0, expressionPos), key: val.slice(expressionPos + 1, expressionEndPos) }}function next (): number { return str.charCodeAt(++index)}function eof (): boolean { return index >= len}function isStringStart (chr: number): boolean { return chr === 0x22 || chr === 0x27}function parseBracket (chr: number): void { let inBracket = 1 expressionPos = index while (!eof()) { chr = next() if (isStringStart(chr)) { parseString(chr) continue } if (chr === 0x5B) inBracket++ if (chr === 0x5D) inBracket-- if (inBracket === 0) { expressionEndPos = index break } }}function parseString (chr: number): void { const stringQuote = chr while (!eof()) { chr = next() if (chr === stringQuote) { break } }} model根据不同的tag类型,将v-model转化成不同的事件绑定onchange、oninput等export default function model ( el: ASTElement, dir: ASTDirective, _warn: Function): ?boolean { warn = _warn const value = dir.value const modifiers = dir.modifiers const tag = el.tag const type = el.attrsMap.type if (process.env.NODE_ENV !== 'production') { // inputs with type="file" are read only and setting the input's // value will throw an error. if (tag === 'input' && type === 'file') { warn(
<${el.tag} v-model="${value}" type=“file”>:\n+
File inputs are read only. Use a v-on:change listener instead. ) } } if (el.component) { genComponentModel(el, value, modifiers) // component v-model doesn't need extra runtime return false } else if (tag === 'select') { genSelect(el, value, modifiers) } else if (tag === 'input' && type === 'checkbox') { genCheckboxModel(el, value, modifiers) } else if (tag === 'input' && type === 'radio') { genRadioModel(el, value, modifiers) } else if (tag === 'input' || tag === 'textarea') { genDefaultModel(el, value, modifiers) } else if (!config.isReservedTag(tag)) { genComponentModel(el, value, modifiers) // component v-model doesn't need extra runtime return false } else if (process.env.NODE_ENV !== 'production') { warn(
<${el.tag} v-model="${value}">: +
v-model is not supported on this element type. + 'If you are working with contenteditable, it\'s recommended to ' + 'wrap a library dedicated for that purpose inside a custom component.' ) } // ensure runtime directive metadata return true}genComponentModel返回设置value值的一个对象集合export function genComponentModel ( el: ASTElement, value: string, modifiers: ?ASTModifiers): ?boolean { const { number, trim } = modifiers || {} const baseValueExpression = '$$v' let valueExpression = baseValueExpression if (trim) { valueExpression =
(typeof ${baseValueExpression} === ‘string’+
? ${baseValueExpression}.trim()+
: ${baseValueExpression}) } if (number) { valueExpression =
_n(${valueExpression}) } const assignment = genAssignmentCode(value, valueExpression) el.model = { value:
(${value}), expression:
"${value}", callback:
function (${baseValueExpression}) {${assignment}} // 类似 function ($$v) { data=_n($$v.tirm()) } }} genAssignmentCode/** * Cross-platform codegen helper for generating v-model value assignment code. * 返回类似 data=_n($$v.tirm()); * $set(data, key, _n($$v.tirm())); */ export function genAssignmentCode ( value: string, assignment: string): string { const res = parseModel(value) if (res.key === null) { return
${value}=${assignment} } else { return
$set(${res.exp}, ${res.key}, ${assignment})` }} addHandler首先检测modifiers参数是否存在,并根据参数中特定属性去重写name参数再根据是否有native修饰符来决定向el.nativeEvents还是el.events添加对应名称的属性值{value: value.trim()}export function addHandler ( el: ASTElement, name: string, value: string, modifiers: ?ASTModifiers, important?: boolean, warn?: Function) { modifiers = modifiers || emptyObject // warn prevent and passive modifier / istanbul ignore if / if ( process.env.NODE_ENV !== ‘production’ && warn && modifiers.prevent && modifiers.passive ) { warn( ‘passive and prevent can't be used together. ’ + ‘Passive handler can't prevent default event.’ ) } // check capture modifier if (modifiers.capture) { delete modifiers.capture name = ‘!’ + name // mark the event as captured } if (modifiers.once) { delete modifiers.once name = ‘~’ + name // mark the event as once } / istanbul ignore if / if (modifiers.passive) { delete modifiers.passive name = ‘&’ + name // mark the event as passive } // normalize click.right and click.middle since they don’t actually fire // this is technically browser-specific, but at least for now browsers are // the only target envs that have right/middle clicks. if (name === ‘click’) { if (modifiers.right) { name = ‘contextmenu’ delete modifiers.right } else if (modifiers.middle) { name = ‘mouseup’ } } let events if (modifiers.native) { delete modifiers.native events = el.nativeEvents || (el.nativeEvents = {}) } else { events = el.events || (el.events = {}) } const newHandler: any = { value: value.trim() } if (modifiers !== emptyObject) { newHandler.modifiers = modifiers } const handlers = events[name] / 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}