基本使用
<div id="app">
<input type="text" v-model="value1">
</div>
new Vue({
el: '#app',
data() {
return {
value1: ''
}
}
})
回顾一下模板到真实节点的过程
AST树的解析
processAttrs
// 处理模板属性
function processAttrs(el) {
var list = el.attrsList;
var i, l, name, rawName, value, modifiers, syncGen, isDynamic;
for (i = 0, l = list.length; i < l; i++) {
name = rawName = list[i].name; // v-on:click
value = list[i].value; // doThis
if (dirRE.test(name)) { // 1.针对指令的属性处理
···
if (bindRE.test(name)) { // v-bind分支
···
} else if(onRE.test(name)) { // v-on分支
···
} else { // 除了v-bind,v-on之外的普通指令
···
// 普通指令会在AST树上添加directives属性
addDirective(el, name, rawName, value, arg, isDynamic, modifiers, list[i]);
if (name === 'model') {
checkForAliasModel(el, value);
}
}
} else {
// 2. 普通html标签属性
}
}
}
addDirective
// 添加directives属性
function addDirective (el,name,rawName,value,arg,isDynamicArg,modifiers,range) {
(el.directives || (el.directives = [])).push(rangeSetItem({
name: name,
rawName: rawName,
value: value,
arg: arg,
isDynamicArg: isDynamicArg,
modifiers: modifiers
}, range));
el.plain = false;
}
// AST
{
directives: {
{
rawName: 'v-model',
value: 'value',
name: 'v-model',
modifiers: undefined
}
}
}
render函数生成
genData
function genData(el, state) {
var data = '{';
// 指令的处理
var dirs = genDirectives(el, state);
··· // 其他属性,指令的处理
// 针对组件的v-model处理
if (el.model) {
data += "model:{value:" + (el.model.value) + ",callback:" + (el.model.callback) + ",expression:" + (el.model.expression) + "},";
}
return data
}
genDirectives
// directives render字符串的生成
function genDirectives (el, state) {
// 拿到指令对象
var dirs = el.directives;
if (!dirs) { return }
// 字符串拼接
var res = 'directives:[';
var hasRuntime = false;
var i, l, dir, needRuntime;
for (i = 0, l = dirs.length; i < l; i++) {
dir = dirs[i];
needRuntime = true;
// 对指令ast树的重新处理,即state.directives['model'];
var gen = 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) + ']'
}
}
var directive$1 = {
model: model,
text: text,
html, html
}
var baseOptions = {
···
// 指令选项
directives: directives$1,
};
// 编译时传入选项配置
createCompiler(baseOptions)
model
function model (el,dir,_warn) {
warn$1 = _warn;
// 绑定的值
var value = dir.value;
var modifiers = dir.modifiers;
var tag = el.tag;
var type = el.attrsMap.type;
{
// 这里遇到type是file的html,如果还使用双向绑定会报出警告。
// 因为File inputs是只读的
if (tag === 'input' && type === 'file') {
warn$1(
"<" + (el.tag) + " v-model=\"" + value + "\" type=\"file\">:\n" +
"File inputs are read only. Use a v-on:change listener instead.",
el.rawAttrsMap['v-model']
);
}
}
//组件上v-model的处理
if (el.component) {
genComponentModel(el, value, modifiers);
// component v-model doesn't need extra runtime
return false
} else if (tag === 'select') {
// select表单
genSelect(el, value, modifiers);
} else if (tag === 'input' && type === 'checkbox') {
// checkbox表单
genCheckboxModel(el, value, modifiers);
} else if (tag === 'input' && type === 'radio') {
// radio表单
genRadioModel(el, value, modifiers);
} else if (tag === 'input' || tag === 'textarea') {
// 普通input,如 text, 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 {
// 如果不是表单使用v-model,同样会报出警告,双向绑定只针对表单控件。
warn$1(
"<" + (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
}
genDefaultModel
function genDefaultModel (el,value,modifiers) {
var type = el.attrsMap.type;
// v-model和v-bind值相同值,有冲突会报错
{
var value$1 = el.attrsMap['v-bind:value'] || el.attrsMap[':value'];
var typeBinding = el.attrsMap['v-bind:type'] || el.attrsMap[':type'];
if (value$1 && !typeBinding) {
var binding = el.attrsMap['v-bind:value'] ? 'v-bind:value' : ':value';
warn$1(
binding + "=\"" + value$1 + "\" conflicts with v-model on the same element " +
'because the latter already expands to a value binding internally',
el.rawAttrsMap[binding]
);
}
}
// modifiers存贮的是v-model的修饰符。
var ref = modifiers || {};
// lazy,trim,number是可供v-model使用的修饰符
var lazy = ref.lazy;
var number = ref.number;
var trim = ref.trim;
var needCompositionGuard = !lazy && type !== 'range';
// lazy修饰符将触发同步的事件从input改为change
var event = lazy ? 'change' : type === 'range' ? RANGE_TOKEN : 'input';
//获取v-model绑定的value的默认求值表达式
var valueExpression = '$event.target.value';
// 过滤用户输入的首尾空白符
if (trim) {
valueExpression = "$event.target.value.trim()";
}
// 将用户输入转为数值类型
if (number) {
valueExpression = "_n(" + valueExpression + ")";
}
// genAssignmentCode函数是为了处理v-model的格式,允许使用以下的形式: v-model="a.b" v-model="a[b]"
// code为改变后的回调内容的函数体
var code = genAssignmentCode(value, valueExpression);
if (needCompositionGuard) {
// 保证了不会在输入法组合文字过程中得到更新
code = "if($event.target.composing)return;" + code;
}
// 添加value属性,即隐式的 :value='v-model变量'
addProp(el, 'value', ("(" + value + ")"));
// 绑定事件
addHandler(el, event, code, null, true);
if (trim || number) {
addHandler(el, 'blur', '$forceUpdate()');
}
}
function genAssignmentCode (value,assignment) {
// 处理v-model的格式,v-model="a.b" v-model="a[b]"
var res = parseModel(value);
if (res.key === null) {
// 普通情形
return (value + "=" + assignment)
} else {
// 对象形式
return ("$set(" + (res.exp) + ", " + (res.key) + ", " + assignment + ")")
}
}
function genData$2 (el, state) {
var data = '{';
// 已经分析过的genDirectives
var dirs = genDirectives(el, state);
// 处理props
if (el.props) {
data += "domProps:" + (genProps(el.props)) + ",";
}
// 处理事件
if (el.events) {
data += (genHandlers(el.events, false)) + ",";
}
}
input标签所有属性,包括指令相关的内容都是以data属性的形式作为参数的整体传入_c(即:createElement)函数。
input type的类型,在data属性中,以attrs键值对存在。
v-model会有对应的directives属性描述指令的相关信息。
为什么说v-model是一个语法糖,从render函数的最终结果可以看出,它最终以两部分形式存在于input标签中,一个是将value以props的形式存在(domProps)中,另一个是以事件的形式存储input事件,并保留在on属性中。
事件用$event.target.composing属性来保证不会在输入法组合文字过程中更新数据
patch真实节点
patch过后走patchVnode,patchVnode过程是一个真实节点创建的过程,其中的关键是createElm方法
function createElm() {
···
// 针对指令的处理
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue);
}
}
invokeCreateHooks
因此v-model语法糖最终反应的结果,是通过监听表单控件自身的input事件(其他类型有不同的监听事件类型),去影响自身的value值。
当v-model绑定到组件上时
<child v-model="message"></child>
AST生成阶段
function model() {
...
if (!config.isReservedTag(tag)) {
genComponentModel(el, value, modifiers);
}
...
}
function genComponentModel (el,value,modifiers) {
var ref = modifiers || {};
var number = ref.number;
var trim = ref.trim;
var baseValueExpression = '?v';
var valueExpression = baseValueExpression;
if (trim) {
valueExpression =
"(typeof " + baseValueExpression + " === 'string'" +
"? " + baseValueExpression + ".trim()" +
": " + baseValueExpression + ")";
}
if (number) {
valueExpression = "_n(" + valueExpression + ")";
}
var assignment = genAssignmentCode(value, valueExpression);
// 在ast树上添加model属性,其中有value,expression,callback属性
el.model = {
value: ("(" + value + ")"),
expression: JSON.stringify(value),
callback: ("function (" + baseValueExpression + ") {" + assignment + "}")
};
}
最终结果类似
{
model: {
callback: "function ($$v) {message=$$v}"
expression: ""message""
value: "(message)"
}
}
经过对AST树的处理后,回到genData$2的流程,由于有了model属性,父组件拼接的字符串会做进一步处理。
function genData$2 (el, state) {
var data = '{';
var dirs = genDirectives(el, state);
···
// v-model组件的render函数处理
if (el.model) {
data += "model:{value:" + (el.model.value) + ",callback:" + (el.model.callback) + ",expression:" + (el.model.expression) + "},";
}
···
return data
}
子组件的创建阶段
createComponent
function createComponent() {
// transform component v-model data into props & events
if (isDef(data.model)) {
// 处理父组件的v-model指令对象
transformModel(Ctor.options, data);
}
}
transformModel
function transformModel (options, data) {
// prop默认取的是value,除非配置上有model的选项
var prop = (options.model && options.model.prop) || 'value';
// event默认取的是input,除非配置上有model的选项
var event = (options.model && options.model.event) || 'input'
// vnode上新增props的属性,值为value
;(data.attrs || (data.attrs = {}))[prop] = data.model.value;
// vnode上新增on属性,标记事件
var on = data.on || (data.on = {});
var existing = on[event];
var callback = data.model.callback;
if (isDef(existing)) {
if (
Array.isArray(existing)
? existing.indexOf(callback) === -1
: existing !== callback
) {
on[event] = [callback].concat(existing);
}
} else {
on[event] = callback;
}
}