本文已参与掘金创作者训练营第三期「话题写作」赛道,详情查看:掘力计划|创作者训练营第三期正在进行,「写」出个人影响力。
更多文章
[vue2]熬夜编写为了让你们通俗易懂的去深入理解vue-router并手写一个
[vue2]熬夜编写为了让你们通俗易懂的去深入理解vuex并手写一个
[vue2]熬夜编写为了让你们通俗易懂的去深入理解nextTick原理
[vue2]熬夜编写为了让你们通俗易懂的去深入理解双向绑定以及解决监听Array数组变化问题
熬夜不易,点个赞再走吧
概念
value+input方法的语法糖
可绑定:input,checkbox,select,textarea,radio
源码
因为根据可绑定的类型不同,会有不同的处理方法,因此需要默认一项
此默认绑定input,v-model绑定值为value,默认在注释中逐行解释
引入:在模板的编译阶段, v-model跟其他指令一样,会被解析到 el.directives 中,之后会通过genDirectives方法处理这些指令
genDirectives
src/compiler/codegen/index.js
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;
var gen = state.directives[dir.name];
if (gen) {
needRuntime = !!gen(el, dir, state.warn);
}
}
指令参数,directives中有个model,然后赋值给dirs
var dirs = el.directives;
dirs是个数组,genDirectives方法中遍历处理这些指令
for (i = 0, l = dirs.length; i < l; i++) {
var gen = state.directives[dir.name];
这里等于var gen = state.directives[model]
;
因此gen = model()
然后执行model()
if (gen) {
needRuntime = !!gen(el, dir, state.warn);
}
执行model()
model
那我们再来看看model这个方法里面具体做了些什么事情,先上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;
{
// inputs with type="file" are read only and setting the input's
// value will throw an error.
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']
);
}
}
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 {
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
}
value:model值
tag:model,在input中使用就是input
type: 绑定类型
var value = dir.value;
var modifiers = dir.modifiers;
var tag = el.tag;
var type = el.attrsMap.type;
判断绑定的什么,通过对应的类型判断进入不同的执行方法\
其中根据判断分别进入以下分支
- 组件 — 执行genComponentModel方法
- select — 执行genSelect方法
- checkbox — input中使用且是checkbox类型执行genCheckboxModel方法
- radio — input中使用且是radio类型执行genRadioModel方法,
- textarea — 执行genDefaultModel方法
- input — 执行genDefaultModel方法
- neither — 都不是则抛出异常
if (el.component) {
genComponentModel(el, value, modifiers);
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);
return false
} else {
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']
);
}
model方法根据传入的参数对tag的类型进行判断,调用不同的处理逻辑,本demo中tag的类型为input,所以会执行genDefaultModel
方法
进入到genDefaultModel执行方法
genDefaultModel
input --- 执行genDefaultModel方法
function genDefaultModel (el,value,modifiers) { }
是否同时具有指令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]
);
}
}
vue表单中有多种可选修饰符
接下来开始获取修饰符lazy, number及trim
- .lazy 取代input监听change事件
- .number 输入字符串转为数字
- .trim 输入首尾空格过滤
var ref = modifiers || {};
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';
var valueExpression = '$event.target.value';
.trim 输入首尾空格过滤
if (trim) {
valueExpression = "$event.target.value.trim()";
}
.number 输入字符串转为数字
if (number) {
valueExpression = "_n(" + valueExpression + ")";
}
接下来处理input
获取code
var code = genAssignmentCode(value, valueExpression);
if (needCompositionGuard) {
code = "if($event.target.composing)return;" + code;
}
$event.target.composing
记录我们在输入文字时,处于未确认状态的文字
if($event.target.composing)return;不记录用户未确定的输入
它会同样会触发input事件,但不会触发v-model更新数据
其中
code = "if($event.target.composing)return;" + code;
等于
code = "if($event.target.composing)return;" + $event.target.value
\
value=$event.target.value
在genAssignmentCode方法中返回
那现在进入genAssignmentCode
genAssignmentCode
生成model中绑定的value值,返回code
function genAssignmentCode ( value, assignment ) {
var res = parseModel(value);
}
当res.key === null
时,返回value=$event.target.value
if (res.key === null) {
return (value + "=" + assignment)
}
但我们不走这里,当obj.value时会进入这里
返回$set(obj, "value", $event.target.value)”
else {
return ("$set(" + (res.exp) + ", " + (res.key) + ", " + assignment + ")")
}
接下来进入$set
function set (target, key, val) {}
- value属性是否一开始就在obj中
- 如果存在就只是进行单纯的赋值
- 不存在的话在进行响应式操作
function set (target, key, val) {
if (key in target && !(key in Object.prototype)) {
target[key] = val;
return val
}
}
function set (target, key, val) {
if (key in target && !(key in Object.prototype)) {
target[key] = val;
return val
}
// defineReactive为value绑定getter()和setter()
defineReactive$$1(ob.value, key, val);
ob.dep.notify();
return val
}
在addProp(el, 'value', ("(" + value + ")"));
这一行有提到动态绑定value,那么进入addProp里看一下
addProp
首先判断el上有没有props
如果没有的话创建props并赋值为一个空数组
然后拼接对象并加入到props中
function addProp (el, name, value, range, dynamic) {
(el.props || (el.props = [])).push(rangeSetItem({ name: name, value: value, dynamic: dynamic }, range));
el.plain = false;
}
addProp的作用是让input动态绑定value
让原本的<input v-model="value">变成<input v-bind:value="value">
然后继续执行addHandler
addHandler的作用是让input动态绑定input
让原本的<input v-bind:value="value">
变成<input :value="value" @input="value=$event.target.value">
最后根据render()生成最终渲染,over