v-model是Vue用於表單元素上建立雙向數據綁定,它本質是一個語法糖,在單向數據綁定的基礎上,增長了監聽用戶輸入事件並更新數據的功能。請看官網對v-model的介紹:https://cn.vuejs.org/v2/api/#...javascript
Vue初始化組件時經過genDirectives(el,state)初始化指令。(這裏的el已經經過parseHTML將html結構轉換成Vue的AST語法樹,詳情見:https://segmentfault.com/a/11...,state是根據用戶定義組件的options新建的CodegenState對象)。html
<div id="test"> 請輸入:<input type="text" v-model="message"><br/> 你輸入的是:<input type="text" v-model="message" disabled > </div>
當用戶在html頁面上寫了v-model指令進行數據雙向綁定,Vue經過state找到model指令對應的方法model(el,dir,_warn),並執行該方法。(這裏雙!!表示將函數返回結果轉換成Boolean類型)vue
var gen = state.directives[dir.name]; if (gen) { needRuntime = !!gen(el, dir, state.warn); }
if (el.component) { genComponentModel(el, value, modifiers); 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); 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.' ); }
model()根據表單元素的tag標籤以及type屬性的值,調用不一樣的方法也就驗證了官網所說的「隨表單控件類型不一樣而不一樣。」這裏調用的就是genDefaultModel().java
var type = el.attrsMap.type;
獲取表單元素的類型,此處type='text'.segmentfault
{ 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' ); } }
檢測該表單元素是否同時有v-model綁定和v-bind:value。api
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';
獲取修飾符lazy,number及trim.編輯器
var valueExpression = '$event.target.value'; if (trim) { valueExpression = "$event.target.value.trim()"; } if (number) { valueExpression = "_n(" + valueExpression + ")"; }
定義變量valueExpression,本例子的狀況valueExpression爲'$event.target.value'。ide
var code = genAssignmentCode(value, valueExpression);
function genAssignmentCode ( value, assignment ) { var res = parseModel(value); if (res.key === null) { return (value + "=" + assignment) } else { return ("$set(" + (res.exp) + ", " + (res.key) + ", " + assignment + ")") } }
經過genAssignmentCode()方法生成v-model value值得代碼。根據本文的例子返回的結果就是"message=$event.target.value"。函數
if (needCompositionGuard) { code = "if($event.target.composing)return;" + code; }
添加event.target.composing判斷。event.target.composing用於判斷這次input事件是不是IME構成觸發的,若是是IME構成,直接return。IME 是輸入法編輯器(Input Method Editor) 的英文縮寫,IME構成指咱們在輸入文字時,處於未確認狀態的文字。如圖:測試
帶下劃線的ceshi就屬於IME構成,它會一樣會觸發input事件,但不會觸發v-model更新數據。
addProp(el, 'value', ("(" + value + ")"));
function addProp (el, name, value) { (el.props || (el.props = [])).push({ name: name, value: value }); el.plain = false; }
給el添加prop
addHandler(el, event, code, null, true);
function addHandler (el,name,value,modifiers,important,warn){ /*其餘代碼省略*/ var newHandler = { value: value.trim() }; var handlers = events[name]; if (Array.isArray(handlers)) { important ? handlers.unshift(newHandler) : handlers.push(newHandler); } else if (handlers) { events[name] = important ? [newHandler, handlers] : [handlers, newHandler]; } else { events[name] = newHandler; } }
將code做爲el的對應event處理的方法handler,此處events['input']=if($event.target.composing)return;message =$event.target.value
最後原來的html結構就由:
<input type="text" v-model="message">
變成了:
<input type="text" :value="message" @input="if($event.target.composing)return;message =$event.target.value">
若是添加了trim修飾符
原來的html結構就由:
<input type="text" v-model.trim="message">
變成了:
<input type="text" :value="message" @input="if($event.target.composing)return;message =$event.target.value.trim()">
若是添加了lazy修飾符
原來的html結構就由:
<input type="text" v-model.lazy="message">
變成了:
<input type="text" :value="message" @change="if($event.target.composing)return;message =$event.target.value">
若是添加了number修飾符
原來的html結構就由:
<input type="text" v-model.number="message">
變成了:
<input type="text" :value="message" @change="if($event.target.composing)return;message = _n($event.target.value)">
這裏的_n是在installRenderHelpers裏面定義的,指向toNumber方法。
function installRenderHelpers (target) { /*其餘代碼省略*/ target._n = toNumber; }
而toNumber就是使用parseFloat函數來轉的數字,再使用isNaN判斷轉換結果,若是結果是NaN,那麼就返回原字符串,不然返回轉爲數字後的結果:
function toNumber (val) { var n = parseFloat(val); return isNaN(n) ? val : n }
注意:number修飾符不能限制輸入的內容,就算輸入的不是數字,也可能會被轉換,如輸入'1次測試',轉換的結果就是1