Vue源碼解讀之v-model

v-model簡介

v-model是Vue用於表單元素上建立雙向數據綁定,它本質是一個語法糖,在單向數據綁定的基礎上,增長了監聽用戶輸入事件並更新數據的功能。請看官網對v-model的介紹:https://cn.vuejs.org/v2/api/#...javascript

v-model源碼解析

genDirectives

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);
    }

model

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

genDefaultModel

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.編輯器

  • .lazy 取代input監聽change事件
  • .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構成指咱們在輸入文字時,處於未確認狀態的文字。如圖:測試

clipboard.png

帶下劃線的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

clipboard.png

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

相關文章
相關標籤/搜索