共计 15996 个字符,预计需要花费 40 分钟才能阅读完成。
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
}
processFor
extend(el, res),el.for, el.alias, el.iterator1, el.iterator2
export 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
}
processElement
export 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)
}
}
}
}
processComponent
function 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, expressionEndPos
type 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
}