說到v-model,就想到了雙向數據綁定,而且往往最常見的是在表單元素<input>,<textarea>,<select>中的使用。
那麼為什麼v-model雙向數據綁定,自動更新元素呢?
v-model 在內部為不同的輸入元素使用不同的屬性並拋出不同的事件:
text 和 textarea 元素使用 value 屬性和 input 事件;
checkbox 和 radio 使用 checked 屬性和 change 事件;
select 字段將 value 作為 prop 並將 change 作為事件。
因此我們將來好好探討一下:
- vue v-model源碼分析
- text和textarea標籤的value屬性和input事件及源碼genDefaultModel函數
- input標籤的checkbox和radio類型的checked屬性和change事件及源碼genCheckboxModel函數和genRadioModel函數
- select標籤的value屬性和change事件及源碼genSelect函數
- v-model的.lazy,.number,.trim修飾符源碼分析
vue 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 if (process.env.NODE_ENV !== 'production') {
...
}
源碼地址:web/compiler/directives/model.js 37~61行
text和textarea標籤的value屬性和input事件及genDefaultModel函數
<!--text類型-->
<input v-model="singleMsg">
<!--textarea類型-->
<textarea v-model="multiMsg"></textarea>
等價於:
<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>
真的是這樣嗎?我們來看看源碼。
genDefaultModel函數
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()')
}
}
源碼分析在源碼中有註釋。
通過源碼我們可以看出:
input(type=“text”)和textarea的v-model,通過value prop獲得值,最終被解析為設置value attribute和input(若設置lazy,則觸發change)事件,從而實現雙向綁定。
源碼地址:web/compiler/directives/model.js 127~147行
input標籤的checkbox和radio類型的checked屬性和change事件及源碼genCheckboxModel函數和genRadioModel函數
<!--checkbox類型-->
<input type="checkbox" v-model="checkboxCtrl">
<!--input類型-->
<input type="radio" v-model="radioCtrl">
等價於:
<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">
genCheckboxModel函數
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
)
}
genRadioModel函數
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標籤的value屬性和change事件及源碼genSelect函數
<!--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>
genSelect 函數
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行
v-model的.lazy,.number,.trim修飾符源碼分析
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})`
}
- lazy僅僅是一個判斷觸發標籤change還是input事件的標識符,lazy為true時,觸發change
- trim僅僅是調用了String.prototype.trim()
- number這是vue的_n函數,而這個函數其實是一個StringToNumber函數,相當簡單
/**
* 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
}
源碼地址:shared/util.js 97~100行
原文:https://segmentfault.com/a/1190000022064240
站長推薦
1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲
2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入
鏈接: http://www.fly63.com/article/detial/8269