v-model 是 Vue 中的一个指令,用于实现表单元素与 Vue 实例中数据的双向绑定。这意味着当表单元素的值发生变化时,Vue 实例中的数据也会随之更新
本质上是语法糖 结合了v-bind和v-on两个指令
示例代码
new Vue({
el: '#app',
data () {
return {
msg: 'Hello, msg'
}
},
template: `<input v-model="msg" />`
})
processElement方法中调用processAttrs来处理标签上面解析的各种属性
export function processElement (
element: ASTElement,
options: CompilerOptions
) {
// ...省略代码
processAttrs(element)
return element
}
进入processAttrs这个方法中 用于构建抽象的语法树
export const dirRE = process.env.VBIND_PROP_SHORTHAND
? /^v-|^@|^:|^\.|^#/
: /^v-|^@|^:|^#/
const argRE = /:(.*)$/
function processAttrs (el) {
const list = el.attrsList
let i, l, name, rawName, value, modifiers, syncGen, isDynamic
for (i = 0, l = list.length; i < l; i++) {
name = rawName = list[i].name
value = list[i].value
if (dirRE.test(name)) {
el.hasBindings = true
// modifiers省略代码
if (bindRE.test(name)) {
// v-bind省略代码
} else if (onRE.test(name)) {
// v-on省略代码
} else {
// normal directives
name = name.replace(dirRE, '')
// parse arg
//先使用dirRE正则表达式把v-model字符串中的v-前缀去掉,
//此时name的值就变成了model
//它又使用了argRE正则表达式来匹配指令参数
//示例
// const template = `<input v-model:value="msg" />`
// 匹配到的指令参数
//const arg = 'value'
const argMatch = name.match(argRE)
let arg = argMatch && argMatch[1]
isDynamic = false
if (arg) {
name = name.slice(0, -(arg.length + 1))
if (dynamicArgRE.test(arg)) {
arg = arg.slice(1, -1)
isDynamic = true
}
}
addDirective(el, name, rawName, value, arg, isDynamic, modifiers, list[i])
if (process.env.NODE_ENV !== 'production' && name === 'model') {
checkForAliasModel(el, value)
}
}
} else {
// ...省略代码
}
}
}
处理完毕后进入调用addDirective方法,给ast对象添加directives属性
export 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树如下所示
const ast = {
type: 1,
tag: 'input',
attrsList: [
{ name: 'v-model', value: 'msg' }
],
attrsMap: {
'v-model': 'msg'
},
directives: [
{ name: 'model', rawName: 'v-model', value: 'msg' }
]
}
codegen代码生成阶段,会在genData方法中调用genDirectives来处理指令
export function genData (el: ASTElement, state: CodegenState): string {
let data = '{'
const dirs = genDirectives(el, state)
if (dirs) data += dirs + ','
// ...省略代码
return data
}
function genDirectives (el: ASTElement, state: CodegenState): string | void {
const dirs = el.directives
if (!dirs) return
let res = 'directives:['
let hasRuntime = false
let i, l, dir, needRuntime
for (i = 0, l = dirs.length; i < l; i++) {
dir = dirs[i]
needRuntime = true
const gen: DirectiveFunction = state.directives[dir.name]
if (gen) {
// compile-time directive that manipulates AST.
// returns true if it also needs a runtime counterpart.
needRuntime = !!gen(el, dir, state.warn)
}
if (needRuntime) {
hasRuntime = true
res += `{name:"${dir.name}",rawName:"${dir.rawName}"${
dir.value ? `,value:(${dir.value}),expression:${JSON.stringify(dir.value)}` : ''
}${
dir.arg ? `,arg:${dir.isDynamicArg ? dir.arg : `"${dir.arg}"`}` : ''
}${
dir.modifiers ? `,modifiers:${JSON.stringify(dir.modifiers)}` : ''
}},`
}
}
if (hasRuntime) {
return res.slice(0, -1) + ']'
}
}
与其他指令不同 state.directives,这个属性是在CodegenState类的构造函数中被处理的
export class CodegenState {
options: CompilerOptions;
warn: Function;
transforms: Array<TransformFunction>;
dataGenFns: Array<DataGenFunction>;
directives: { [key: string]: DirectiveFunction };
maybeComponent: (el: ASTElement) => boolean;
onceId: number;
staticRenderFns: Array<string>;
pre: boolean;
constructor (options: CompilerOptions) {
this.options = options
// ...省略代码
this.directives = extend(extend({}, baseDirectives), options.directives)
// ...省略代码
}
}
directives中 v-model中
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.`,
el.rawAttrsMap['v-model']
)
}
}
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.',
el.rawAttrsMap['v-model']
)
}
// ensure runtime directive metadata
return true
}
function genDefaultModel (
el: ASTElement,
value: string,
modifiers: ?ASTModifiers
): ?boolean {
// ...省略代码
addProp(el, 'value', `(${value})`)
addHandler(el, event, code, null, true)
// ...省略代码
}
● addProp:调用addProp是为了给ast添加一个value的props属性。
● addHandler:调用addHandler是为了给ast添加一个事件监听,至于到底监听什么事件取决于v-model作用于什么标签。
所以处理之后多了props和events属性
export function genData (el: ASTElement, state: CodegenState): string {
let data = '{'
// directive
const dirs = genDirectives(el, state)
if (dirs) data += dirs + ','
// ...省略代码
// DOM props
if (el.props) {
data += `domProps:${genProps(el.props)},`
}
// event handlers
if (el.events) {
data += `${genHandlers(el.events, false)},`
}
// ...省略代码
return data
}
其实学源码学到后续感觉有点懵 然后就是复习js 感觉红宝书讲的挺多的 马上也要期末考试了 希望期末平稳度过。