一、v-model原理
v-model指令实质上就是一个语法糖,默认用于支持form表单类型控件实现双向绑定。二、源代码
用如下这段代码来探寻v-model的实现原理<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>数据绑定</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<div id="root">
双向数据绑定:<input type="text" v-model.lazy="name"><br/>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
new Vue({
el:'#root',
data:{
name:'哇哈哈'
}
})
</script>
</html>
Vue.version = '2.6.12';
function Vue (options) {
//...
//调用_init函数
this._init(options);
}
//初始化Mixin
initMixin(Vue);
//在initMixin函数中存在如下代码:
function initMixin (Vue) {
Vue.prototype._init = function (options) {
var vm = this;
//...
// expose real self
vm._self = vm;
initLifecycle(vm);
initEvents(vm);
initRender(vm);
callHook(vm, 'beforeCreate');
initInjections(vm); // resolve injections before data/props
initState(vm);
initProvide(vm); // resolve provide after data/props
callHook(vm, 'created');
//...
if (vm.$options.el) {
//关键点在这里,开始挂载组件。这个$mount调用的是$mount②
vm.$mount(vm.$options.el);
}
};
}
//$mount函数实现①如下所示:
Vue.prototype.$mount = function (
el,
hydrating
) {
el = el && inBrowser ? query(el) : undefined;
return mountComponent(this, el, hydrating)
};
//mountComponent函数如下所示
function mountComponent (
vm,
el,
hydrating
) {
vm.$el = el;
//...
callHook(vm, 'beforeMount');
var updateComponent;
/* istanbul ignore if */
if (config.performance && mark) {
updateComponent = function () {
//...
};
} else {//代码走这里
updateComponent = function () {
//调用vm._update函数
vm._update(vm._render(), hydrating);
};
}
//...
//注册事件函数的入口
new Watcher(vm, updateComponent, noop, {
before: function before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate');
}
}
}, true /* isRenderWatcher */);
//...
if (vm.$vnode == null) {
vm._isMounted = true;
callHook(vm, 'mounted');
}
return vm
}
//Watcher函数具体实现
var Watcher = function Watcher (
vm,
expOrFn,
cb,
options,
isRenderWatcher
) {
//...
if (typeof expOrFn === 'function') {
this.getter = expOrFn;
} else {
this.getter = parsePath(expOrFn);
if (!this.getter) {
this.getter = noop;
warn(
"Failed watching path: \"" + expOrFn + "\" " +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
);
}
}
this.value = this.lazy
? undefined
: this.get();
};
Watcher.prototype.get = function get () {
pushTarget(this);
var value;
var vm = this.vm;
try {
//调用 updateComponent函数
value = this.getter.call(vm, vm);
} catch (e) {
//...
}
return value
};
//vm._update函数具体实现如下:
function lifecycleMixin (Vue) {
Vue.prototype._update = function (vnode, hydrating) {
var vm = this;
var prevEl = vm.$el;
var prevVnode = vm._vnode;
var restoreActiveInstance = setActiveInstance(vm);
vm._vnode = vnode;
// Vue.prototype.__patch__ is injected in entry points
// based on the rendering backend used.
if (!prevVnode) {
// initial render
//关键点初始渲染,vm.__patch__看第三节
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode);
}
restoreActiveInstance();
// update __vue__ reference
if (prevEl) {
prevEl.__vue__ = null;
}
if (vm.$el) {
vm.$el.__vue__ = vm;
}
// if parent is an HOC, update its $el as well
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el;
}
// updated hook is called by the scheduler to ensure that children are
// updated in a parent's updated hook.
};
//...
}
//注意这个mount就是上面$mount①
var mount = Vue.prototype.$mount;
//$mount函数实现②如下所示:
Vue.prototype.$mount = function (
el,
hydrating
) {
el = el && query(el);
//...
//关键点:去函数编译
var ref = compileToFunctions(template, {
outputSourceRange: "development" !== 'production',
shouldDecodeNewlines: shouldDecodeNewlines,
shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this);
//...
//最后通过调用$mount①里面的mountComponent函数
return mount.call(this, el, hydrating)
};
//compileToFunctions函数具体实现如下:
function createCompileToFunctionFn (compile) {
var cache = Object.create(null);
return function compileToFunctions (
template,
options,
vm
) {
//...
// 关键点:进行编译compile
var compiled = compile(template, options);
//...
return (cache[key] = res)
}
}
//compile函数具体实现如下所示:
function createCompilerCreator (baseCompile) {
return function createCompiler (baseOptions) {
function compile (
template,
options
) {
//...
//关键点
var compiled = baseCompile(template.trim(), finalOptions);
//...
}
return {
compile: compile,
compileToFunctions: createCompileToFunctionFn(compile)
}
}
}
//调用createCompilerCreator函数
var createCompiler = createCompilerCreator(function baseCompile (
template,
options
) {
//...
//关键点
var code = generate(ast, options);
//...
});
//generate函数实现如下所示
function generate (
ast,
options
) {
var state = new CodegenState(options);
//关键点
var code = ast ? genElement(ast, state) : '_c("div")';
return {
render: ("with(this){return " + code + "}"),
staticRenderFns: state.staticRenderFns
}
}
//genElement函数实现如下所示
function genElement (el, state) {
//...
//关键点genChildren
var children = el.inlineTemplate ? null : genChildren(el, state, true);
//...
return code
}
}
//genChildren函数具体实现如下:
function genChildren (
el,
state,
checkSkip,
altGenElement,
altGenNode
) {
var children = el.children;
if (children.length) {
var gen = altGenNode || genNode;
return ("[" +
(children.map(function (c) {
//关键点
return gen(c, state); }).join(',')) + "]" + (normalizationType$1 ? ("," + normalizationType$1) : ''))
}
}
//这个gen函数调用的具体实现如下:
function genNode (node, state) {
if (node.type === 1) {
//关键点,再次调用genElement
return genElement(node, state)
} else if (node.type === 3 && node.isComment) {
return genComment(node)
} else {
return genText(node)
}
}
//genElement函数实现如下所示
function genElement (el, state) {
if (el.parent) {
el.pre = el.pre || el.parent.pre;
}
//...
var code;
if (el.component) {
code = genComponent(el.component, el, state);
} else {
var data;
if (!el.plain || (el.pre && state.maybeComponent(el))) {
//关键点
data = genData$2(el, state);
}
var children = el.inlineTemplate ? null : genChildren(el, state, true);
code = "_c('" + (el.tag) + "'" + (data ? ("," + data) : '') + (children ? ("," + children) : '') + ")";
}
// module transforms
for (var i = 0; i < state.transforms.length; i++) {
code = state.transforms[i](el, code);
}
return code
}
//genData$2函数实现如下所示:
function genData$2 (el, state) {
var data = '{';
// 处理指令
var dirs = genDirectives(el, state);
if (dirs) { data += dirs + ','; }
//... 指令拼接
return data
}
//genDirectives函数具体如下:
function genDirectives (el, state) {
//...
//这个会调用到model函数
needRuntime = !!gen(el, dir, state.warn);
//...
}
//model函数具体实现如下:
function model (
el,
dir,
_warn
) {
//...
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') {
//当前demo要走这里
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
}
//genDefaultModel函数如下所示:将具体事件函数拼接成字符串
function genDefaultModel (
el,
value,
modifiers
) {
var type = el.attrsMap.type;
// warn if v-bind:value conflicts with v-model
// except for inputs with v-bind:type
{
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]
);
}
}
var ref = modifiers || {};
var lazy = ref.lazy;
var number = ref.number;
var trim = ref.trim;
var needCompositionGuard = !lazy && type !== 'range';
var event = lazy
? 'change'
: type === 'range'
? RANGE_TOKEN
: 'input';
var valueExpression = '$event.target.value';
if (trim) {
valueExpression = "$event.target.value.trim()";
}
if (number) {
valueExpression = "_n(" + valueExpression + ")";
}
var code = genAssignmentCode(value, valueExpression);
if (needCompositionGuard) {
code = "if($event.target.composing)return;" + code;
}
addProp(el, 'value', ("(" + value + ")"));
addHandler(el, event, code, null, true);
if (trim || number) {
addHandler(el, 'blur', '$forceUpdate()');
}
}
三、vm.__patch__调用顺序
模板编译时期
添加事件时期
function add$1 (
name,
handler,
capture,
passive
) {
//...给元素添加事件,在demo中,此处得name为change,handler是就是针对change事件的回调函数
target$1.addEventListener(
name,
handler,
supportsPassive
? { capture: capture, passive: passive }
: capture
);
}