结论:v-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
}
看具体实现
主要看callBack
/**
* Cross-platform code generation for component v-model
*/
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);
el.model = {
value: ("(" + value + ")"),
expression: JSON.stringify(value),
callback: ("function (" + baseValueExpression + ") {" + assignment + "}")
};
}
我们上面发现callback会执行assignment,也就是genAssignmentCode
/**
* Cross-platform codegen helper for generating v-model value assignment code.
*/
function genAssignmentCode (
value,
assignment
) {
var res = parseModel(value);
if (res.key === null) {
return (value + "=" + assignment)
} else {
return ("$set(" + (res.exp) + ", " + (res.key) + ", " + assignment + ")")
}
}
总结
v-model在emit处理会设置值外,还会$set。也就是增加响应式
// 对我我们手动实现类似v-model,主要也要加上$set。不然数据不是响应的,也就是不会更新到页面中,导致数据不同步
拓展总结
- Vue就是MVVM,力求所有东西都是响应式的(所以能响应的地方会加上响应,除了没法监听到除外,以及给出了补救方法)
- 对于对象:Vue 无法检测 property 的添加或移除
// 如果data中,为form:{}
this.form = {...} // Form及后代所有属性有响应,因为能监听到赋值form的Setter
this.form.name = 'hcl' // 是监听不到的,没有监听属性的添加和移除,proxy可以!
- 对于数组:数组修改操作(这里注意和对象操作的边界),如果不是有赋值操作(其实这个是对象操作),都监听不到
this.list[2] ={} // 这个是数组的操作,数组没有Setter,监听不到
this.list = [1,2] // 这个是对象操作,即给list属性赋值,当然能监听到
this.list.length // 可以监听到长度变化的响应,但不能反馈到list中,也就是监听不到
- 对于数组的补充
由于数组操作没有Setter,所以Vue重写了数组的操作,使之可以响应
var methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
];
/**
* Intercept mutating methods and emit events
*/
methodsToPatch.forEach(function (method) {
// cache original method
var original = arrayProto[method];
def(arrayMethods, method, function mutator () {
var args = [], len = arguments.length;
while ( len-- ) args[ len ] = arguments[ len ];
var result = original.apply(this, args);
var ob = this.__ob__;
var inserted;
switch (method) {
case 'push':
case 'unshift':
inserted = args;
break
case 'splice':
inserted = args.slice(2);
break
}
if (inserted) { ob.observeArray(inserted); }
// notify change
ob.dep.notify();
return result
});
});
- 记住一点:我们需要时刻关注哪些会没有响应,这个会带来隐藏的bug。