說到v-model,就想到了雙向數據綁定,並且每每最多見的是在表單元素<input>,<textarea>,<select>
中的使用。html
那麼爲何v-model雙向數據綁定,自動更新元素呢?前端
v-model 在內部爲不一樣的輸入元素使用不一樣的屬性並拋出不一樣的事件:
text 和 textarea 元素使用 value 屬性和 input 事件;
checkbox 和 radio 使用 checked 屬性和 change 事件;
select 字段將 value 做爲 prop 並將 change 做爲事件。
所以咱們未來好好探討一下:vue
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') { ... }
源碼地址:web/compiler/directives/model.js 37~61行git
<!--text類型--> <input v-model="singleMsg"> <!--textarea類型--> <textarea v-model="multiMsg"></textarea>
等價於:github
<input type="text" v-bind:value="singleMsg" v-on:input="singleMsg=$event.target.value" > <textarea v-bind:value="multiMsg" v-on:input="multiMsg=$event.target.value"></textarea>
真的是這樣嗎?咱們來看看源碼。web
function genDefaultModel ( el: ASTElement, value: string, modifiers: ?ASTModifiers ): ?boolean { const type = el.attrsMap.type // warn if v-bind:value conflicts with v-model // except for inputs with v-bind:type if (process.env.NODE_ENV !== 'production') { ... } const { lazy, number, trim } = modifiers || {} const needCompositionGuard = !lazy && type !== 'range' const event = lazy // 此處咱們沒有傳入lazy修飾符,所以event變量是'input' ? 'change' : type === 'range' ? RANGE_TOKEN : 'input' let valueExpression = '$event.target.value' if (trim) { valueExpression = `$event.target.value.trim()` } if (number) { valueExpression = `_n(${valueExpression})` } let code = genAssignmentCode(value, valueExpression) if (needCompositionGuard) { code = `if($event.target.composing)return;${code}` } addProp(el, 'value', `(${value})`) // <input type="text" v-bind:value="singleMsg" ="singleMsg=$event.target.value" > addHandler(el, event, code, null, true) // 這一步印證了input事件<input v-on:input="..."> if (trim || number) { addHandler(el, 'blur', '$forceUpdate()') } }
源碼分析在源碼中有註釋。segmentfault
經過源碼咱們能夠看出:
input(type=「text」)和textarea的v-model,經過value prop得到值,最終被解析爲設置value attribute和input(若設置lazy,則觸發change)事件,從而實現雙向綁定。微信
源碼地址:web/compiler/directives/model.js 127~147行前端工程師
<!--checkbox類型--> <input type="checkbox" v-model="checkboxCtrl"> <!--input類型--> <input type="radio" v-model="radioCtrl">
等價於:ide
<input type="checkbox" v-bind:value="checkboxCtrl" v-on:change="checkboxCtrl=$event.target.checked"> <input type="radio" v-bind:value="radioCtrl" v-on:change="radioCtrl=$event.target.checked">
function genCheckboxModel ( el: ASTElement, value: string, modifiers: ?ASTModifiers ) { const number = modifiers && modifiers.number const valueBinding = getBindingAttr(el, 'value') || 'null' // 從v-bind的value得到到初始值,印證了 <input v-bind:value="..."> const trueValueBinding = getBindingAttr(el, 'true-value') || 'true' const falseValueBinding = getBindingAttr(el, 'false-value') || 'false' addProp(el, 'checked', // 設置標籤的checked attribute,印證了<input ="checkboxCtrl=$event.target.checked"> `Array.isArray(${value})` + `?_i(${value},${valueBinding})>-1` + ( trueValueBinding === 'true' ? `:(${value})` : `:_q(${value},${trueValueBinding})` ) ) addHandler(el, 'change', // 這一步印證了<input v-on:change="..."> `var $$a=${value},` + '$$el=$event.target,' + `$$c=$$el.checked?(${trueValueBinding}):(${falseValueBinding});` + 'if(Array.isArray($$a)){' + `var $$v=${number ? '_n(' + valueBinding + ')' : valueBinding},` + '$$i=_i($$a,$$v);' + `if($$el.checked){$$i<0&&(${genAssignmentCode(value, '$$a.concat([$$v])')})}` + `else{$$i>-1&&(${genAssignmentCode(value, '$$a.slice(0,$$i).concat($$a.slice($$i+1))')})}` + `}else{${genAssignmentCode(value, '$$c')}}`, null, true ) }
function genRadioModel ( el: ASTElement, value: string, modifiers: ?ASTModifiers ) { const number = modifiers && modifiers.number let valueBinding = getBindingAttr(el, 'value') || 'null' // 印證了 <input v-bind:value="..."> valueBinding = number ? `_n(${valueBinding})` : valueBinding addProp(el, 'checked', `_q(${value},${valueBinding})`) // 設置標籤的checked attribute,印證了<input ="radioCtrl=$event.target.checked"> addHandler(el, 'change', genAssignmentCode(value, valueBinding), null, true) // 這一步印證了<input v-on:change="..."> }
源碼分析在源碼中有註釋。
經過源碼咱們能夠看出:
input(type=「checkbox」)和input(type="radio")的v-model,經過value prop得到值,最終被解析爲設置checked attribute和change事件,從而實現雙向綁定。
源碼地址:web/compiler/directives/model.js 67~96行
<!--select類型--> <select v-model="selectCtrl"> <option>A</option> <option>B</option> <option>C</option> </select>
等價於:
<select v-bind:value="selectValue" v-on:change="selectValue=$event.target.options.selected"> <option>A</option> <option>B</option> <option>C</option> </select>
function genSelect ( el: ASTElement, value: string, modifiers: ?ASTModifiers ) { const number = modifiers && modifiers.number // 這一步印證了<select v-bind:value="selectValue" ...="selectValue=$event.target.options.selected"> const selectedVal = `Array.prototype.filter` + `.call($event.target.options,function(o){return o.selected})` + `.map(function(o){var val = "_value" in o ? o._value : o.value;` + `return ${number ? '_n(val)' : 'val'}})` const assignment = '$event.target.multiple ? $$selectedVal : $$selectedVal[0]' let code = `var $$selectedVal = ${selectedVal};` code = `${code} ${genAssignmentCode(value, assignment)}` // 這一步印證了 <select v-on:change="..."> addHandler(el, 'change', code, null, true) }
源碼分析在源碼中有註釋。
經過源碼咱們能夠看出:
select的v-model,經過value prop得到值,最終被解析爲設置selected attribute和change事件,從而實現雙向綁定。
源碼地址:web/compiler/directives/model.js 110~125行
const { lazy, number, trim } = modifiers || {} ... const event = lazy ? 'change' : type === 'range' ? RANGE_TOKEN : 'input' ... if (trim) { valueExpression = `$event.target.value.trim()` } if (number) { valueExpression = `_n(${valueExpression})` }
/** * Convert an input value to a number for persistence. * If the conversion fails, return original string. */ export function toNumber (val: string): number | string { const n = parseFloat(val) return isNaN(n) ? val : n }
參考資料:
https://cn.vuejs.org/v2/guide...
https://developer.mozilla.org...
https://developer.mozilla.org...
期待和你們交流,共同進步,歡迎你們加入我建立的與前端開發密切相關的技術討論小組:
- SegmentFault技術圈:ES新規範語法糖
- SegmentFault專欄:趁你還年輕,作個優秀的前端工程師
- 知乎專欄:趁你還年輕,作個優秀的前端工程師
- Github博客: 趁你還年輕233的我的博客
- 前端開發QQ羣:660634678
- 微信公衆號: 人獸鬼 / excellent_developers
努力成爲優秀前端工程師!