vue核心面试题:v-model中的实现原理及如何自定义v-model
一、概念1.v-model可以看成是value+input方法的语法糖。2.组件的v-model就是value+input方法的语法糖。3.可以绑定v-model的有:input,checkbox,select,textarea,radio二、源码1.组件的 v-model源码组件的 v-model是默认转化成value+input总结:在组件初始化的时候,回去判断当前数据中有没有model属性,
一、概念
1.v-model可以看成是value+input方法的语法糖。
2.组件的v-model就是value+input方法的语法糖。
3.可以绑定v-model的有:input,checkbox,select,textarea,radio
二、源码
1.组件的 v-model源码
组件的 v-model 是默认转化成value+input
总结:
在组件初始化的时候,回去判断当前数据中有没有model属性,如果有就会使用transformModel方法进行model的转换,transformModel会对数据的model属性进行判断,如果没有prop属性默认就是value,如果没有event属性就是input,然后将值赋给value,给on绑定input的事件,对于的就是callback。
自定义model时,只需要在组件中传入model的prop和event属性。
const VueTemplateCompiler = require('vue-template-compiler');
const ele = VueTemplateCompiler.compile('<el-checkbox v-model="check"></elcheckbox>');
// 解析后
with(this) {
return _c('el-checkbox', {
model: { // v-model解析后
value: (check),
callback: function ($$v) {
check = $$v
},
expression: "check"
}
})
}
(1)组件初始化时,会判断是否有model属性。(src/core/vdom/create-component.js)
在createComponent方法中
// 如果当前数据有model属性,就会使用transformModel转换model
if (isDef(data.model)) {
transformModel(Ctor.options, data)
}
(2)transformModel 方法用来转换model(src/core/vdom/create-component.js)
function transformModel (options, data: any) {
// 如果没有prop属性默认就是value
const prop = (options.model && options.model.prop) || 'value'
// 如果没有event属性,默认就是input
const event = (options.model && options.model.event) || 'input'
;(data.attrs || (data.attrs = {}))[prop] = data.model.value// data.attrs.value="xxx"
const on = data.on || (data.on = {})
const existing = on[event] // 给on绑定input的事件,对应的函数就是callback
const 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 //
}
}
(3)自定义v-model
Vue.component('el-checkbox', {
template:`<input type="checkbox" :checked="check" @change="$emit('change',$event.target.checked)">`,
model:{
prop:'check', // 更改默认的value的名字
event:'change' // 更改默认的方法名
},
props: {
check: Boolean
}
})
2.原生的 v-model
原生的 v-model ,会根据标签的不同生成不同的事件和属性
总结:
如果是普通标签,它一看是input就会直接绑成domProps,值是value,把on绑定input,在除了value和input外还会加上一个指令directives,如果input上面写上type是checkbox时,这时候domProps就会变成checked,on的事件就会变成change,它在编译的时候会根据type生成不同的事件和属性。
在编译的时候,如果有v-model,就会执行这个指令,找对指令下对应的model,就会对他进行判断,根据不同的类型生成不同的内容:
组件---------->genComponentModel方法
select-------->genSelect方法------------------>添加value属性,change事件
checkbox--->genCheckboxModel方法----->添加checked属性,change事件
radio--------->genRadioModel方法----------->添加checked属性,change事件
textarea ---->genDefaultModel方法--------->添加value属性,input事件
input--------->genDefaultModel方法--------->添加value属性,input或change事件,如果lazy是ture就是change
在编译后多出一个directives指令是,是在运行时,在inserted方法中会对元素处理一些关于输入法的问题,对它增加了compositionstart,compositionend,change三个事件
compositionstart:当用户使用拼音输入法开始输入汉字时,这个事件就会被触发。
compositionend:将被触发 (具有特殊字符的触发, 需要一系列键和其他输入, 如语音识别或移动中的字词建议)。
<input v-model="sth" />
// 等同于
<input :value="sth" @input="sth = $event.target.value" />
const VueTemplateCompiler = require('vue-template-compiler');
const ele = VueTemplateCompiler.compile('<input v-model="value"/>');
with(this) {
return _c('input', {
directives: [{
name: "model",
rawName: "v-model",
value: (value),
expression: "value"
}],
domProps: {
"value": (value)
},
on: {
"input": function ($event) {
if ($event.target.composing) return;
value = $event.target.value
}
}
})
}
// 将input添加上type,值是ckeckbox
const ele = VueTemplateCompiler.compile('<input type="checkbox" v-model="value"/>');
// 编译完后domProps的属性就是checked,on绑定的就是change事件
(1)编译时:不同的标签解析出的内容不一样 (src/platforms/web/compiler/directives/model.js)
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']
)
}
(2)运行时:会对元素处理一些关于输入法的问题 (src/platforms/web/runtime/directives/model.js)
inserted (el, binding, vnode, oldVnode) {
if (vnode.tag === 'select') {
// #6903
if (oldVnode.elm && !oldVnode.elm._vOptions) {
mergeVNodeHook(vnode, 'postpatch', () => {
directive.componentUpdated(el, binding, vnode)
})
} else {
setSelected(el, binding, vnode.context)
}
el._vOptions = [].map.call(el.options, getValue)
} else if (vnode.tag === 'textarea' || isTextInputType(el.type)) {
el._vModifiers = binding.modifiers
if (!binding.modifiers.lazy) {
// 对输入法做了一些校验,然后绑定自己的事件
el.addEventListener('compositionstart', onCompositionStart)
el.addEventListener('compositionend', onCompositionEnd)
// Safari < 10.2 & UIWebView doesn't fire compositionend when
// switching focus before confirming composition choice
// this also fixes the issue where some browsers e.g. iOS Chrome
// fires "change" instead of "input" on autocomplete.
el.addEventListener('change', onCompositionEnd)
/* istanbul ignore if */
if (isIE9) {
el.vmodel = true
}
}
}
}
更多推荐
所有评论(0)