從vue.js的源碼分析,input和textarea上的v-model指令到底做了什么


v-model是 vue.js 中用於在表單表單元素上創建雙向數據綁定,它的本質只是一個語法糖,在單向數據綁定的基礎上,增加了監聽用戶輸入事件並更新數據的功能;
對,它本質上只是一個語法糖,但到底是一個什么樣的語法糖呢……?
正好最近我也踩到相關的坑了,就從最簡單的input和textarea元素入手,分析一下v-model這個指令到底做了什么吧

請先確認您已閱讀過官方文檔中關於v-model的部分

沒有任何修飾符的 v-model

v-model 會根據綁定元素的類型,監聽不同的輸入事件,在 input 和 textarea 上,它默認監聽的就是 input 事件;
簡單點說,如果有這樣一段模板:

    <input v-model="name" type="text"/>

那么 v-model 的行為,就比較類似:

    <input :value="name" @input="name = $event.target.value" type="text"/>

關於$event變量,官方文檔有介紹:

有時也需要在內聯語句處理器中訪問原生 DOM 事件。可以用特殊變量 $event 把它傳入方法

雖然上面的例子描述了 v-model 最基礎的行為,但二者的行為也不是完全一樣,這次我踩到的坑也就在這里了……
官方文檔中表單控件綁定章節有這么一條提示:

對於要求 IME (如中文、 日語、 韓語等) 的語言,你會發現v-model不會在 ime 構成中得到更新。如果你也想實現更新,請使用input事件。

IME 的全稱是 Input Method Editor 也就是我們常說的輸入法
所謂 IME 構成,是指我們在輸入文字時,處於未確認狀態的文字,以微軟的中文輸入法舉例:

圖中帶下划線的部分就屬於 IME 構成,這些東西同樣會觸發input事件,但不會觸發v-model更新數據……
本着踩進坑里了就不能白踩的精神,閱讀了一下 vue.js 的源碼:

function genDefaultModel (
  el: ASTElement,
  value: string,
  modifiers: ?ASTModifiers
): ?boolean {
  const type = el.attrsMap.type
  const { lazy, number, trim } = modifiers || {}
  const needCompositionGuard = !lazy && type !== 'range'
  const event = lazy
    ? '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})`)
  addHandler(el, event, code, null, true)
  if (trim || number || type === 'number') {
    addHandler(el, 'blur', '$forceUpdate()')
  }
}

可以看到,v-model在編譯的時候自動添加了一層判斷,根據event.target.composing判斷此次input事件是否是 IME 構成觸發的……然而不知為什么,我沒有在 MDN 上查到這個屬性……
所以 v-model 的實際行為和這樣的模板是一致的:

    <input :value="name" @input="if($event.target.composing)return;name=$event.target.value" type="text"/>

當然也有例外,比如type="range"的 input 元素就不會加這層判斷,再怎么說滑塊也是不會和 IME 構成有關的;

添加了 trim 修飾符的 v-model

trim修飾符的作用就是去除輸入內容兩側的空格,這個沒什么問題……
v-model.trim的行為類似於:

    <input :value="name" @input="if($event.target.composing)return;name=$event.target.value.trim()" type="text"/>

添加了 lazy 修飾符的 v-model

lazy修飾符按照官方的描述是將默認監聽的 input 事件改為監聽 change 事件:

在默認情況下, v-model 在 input 事件中同步輸入框的值與數據 (除了 上述 IME 部分),但你可以添加一個修飾符 lazy ,從而轉變為在 change 事件中同步

而 change 事件一般是在輸入元素失去焦點時才會觸發,所以也不用考慮 IME 構成什么的了,v-model.lazy 的行為就類似於:

    <input :value="name" @change="name=$event.target.value" type="text"/>

添加了 number 修飾符的 v-model

官方文檔說這個修飾符可以自動把輸入內容轉為數字,然而具體是怎么轉的數字並沒有說……

如果想自動將用戶的輸入值轉為 Number 類型(如果原值的轉換結果為 NaN 則返回原值),可以添加一個修飾符 number 給 v-model 來處理輸入值

從之前的源碼來看,vue.js使用了一個_n函數來處理輸入內容,也就是說v-model.number的行為可以這樣模仿:

    <input type="text" :value="name" type="number" @input="if($event.target.composing)return;name =_n($event.target.value)">

……然而這個_n是什么……
既然是能直接用的函數,那肯定能在 runtime 里找到,稍微翻了一下發現這個_n實際上是一個toNumber的工具函數,而toNumber函數是下面這個樣子的:

/**
 * Convert a 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
}

可見,它是使用parseFloat函數來轉的數字,再使用isNaN判斷轉換結果,如果結果是NaN,那么就返回原字符串,否則返回轉為數字后的結果;
所以如果想完整模擬number修飾符,只在內聯語句中寫就不太現實了……姑且嘗試了一下,類似下面這個樣子:

    <input type="text" :value="name" type="number" @input="if($event.target.composing)return;name=isNaN(parseFloat($event.target.value))?$event.target.value:parseFloat($event.target.value)">

好吧,我承認這個修飾符很好用了……(:з)∠)
弄懂這個修飾符到底是什么之后,應該也就不會弄錯了,不過反正都寫到這里了,順便就根據別人問過我的問題,整理一下number修飾符幾個常見的理解誤區吧:

number 修飾符不能限制輸入內容

這個修飾符的作用僅僅是把用戶輸入的內容嘗試轉換一下,如果轉換成功了,那就是成功了,如果沒成功,它也不會多管閑事的不讓你輸入進去;

就算輸入的不是真實的數字,也可能會被轉換

因為使用的是parseFloat函數嘛……所以只要輸入字符串的第一個字符是數字,最后就會被轉成數字,比如這樣

number 修飾符只會嘗試轉換,不會嘗試計算

它只是一個簡單的修飾符……請不要嘗試讓它做太多的事情……就算你輸入一個1+1,更新數據時也不會變成2的……當然,也不會原封不動的把"1+1"這個字符串放進去,因為剛才說過了嘛……只要第一個字符是數字,parseFloat這個函數就能把輸入的字符串給你轉成數字……←A←

以上就是今天要寫的內容啦,如果再有什么新發現,我可能會再補充~
總的來說……vue.js 寫的還是很精致的,也相當好用,源碼也非常清晰,偶爾讀讀原理,想必也會對使用這個框架有或多或少的幫助吧
起碼我覺得自己以后是不會再踩進 v-model 的坑了……
【下次抽時間也研究一下 select 和 checkbox 的 v-model 行為好了……

如果文章中有什么錯誤或者疏漏,請告訴我,我會盡快修改!
關於本文有任何問題也歡迎評論~
轉載請注明來源


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM