一、v-model原理

v-model指令实质上就是一个语法糖,默认用于支持form表单类型控件实现双向绑定。

二、源代码

用如下这段代码来探寻v-model的实现原理
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>数据绑定</title>
		<!-- 引入Vue -->
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
		<div id="root">
			双向数据绑定:<input type="text" v-model.lazy="name"><br/>
		</div>
	</body>

	<script type="text/javascript">
		Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

		new Vue({
			el:'#root',
			data:{
				name:'哇哈哈'
			}
		})
	</script>
</html>
Vue.version = '2.6.12';


function Vue (options) {
  	//...
  	//调用_init函数
   this._init(options);
 }
 
//初始化Mixin
initMixin(Vue);

//在initMixin函数中存在如下代码:
function initMixin (Vue) {
    Vue.prototype._init = function (options) {
      var vm = this;
     //...
      // expose real self
      vm._self = vm;
      initLifecycle(vm);
      initEvents(vm);
      initRender(vm);
      callHook(vm, 'beforeCreate');
      initInjections(vm); // resolve injections before data/props
      initState(vm);
      initProvide(vm); // resolve provide after data/props
      callHook(vm, 'created');

	  //...
      if (vm.$options.el) {
      	//关键点在这里,开始挂载组件。这个$mount调用的是$mount②
        vm.$mount(vm.$options.el);
      }
    };
  }

//$mount函数实现①如下所示:
Vue.prototype.$mount = function (
   el,
   hydrating
 ) {
   el = el && inBrowser ? query(el) : undefined;
   return mountComponent(this, el, hydrating)
 };

//mountComponent函数如下所示
function mountComponent (
    vm,
    el,
    hydrating
  ) {
    vm.$el = el;
    //...
    callHook(vm, 'beforeMount');
 var updateComponent;
    /* istanbul ignore if */
    if (config.performance && mark) {
      updateComponent = function () {
        //...
      };
    } else {//代码走这里
      updateComponent = function () {
      //调用vm._update函数
        vm._update(vm._render(), hydrating);
      };
    }
	//...
	//注册事件函数的入口
    new Watcher(vm, updateComponent, noop, {
      before: function before () {
        if (vm._isMounted && !vm._isDestroyed) {
          callHook(vm, 'beforeUpdate');
        }
      }
    }, true /* isRenderWatcher */);
   	
   	//...
    if (vm.$vnode == null) {
      vm._isMounted = true;
      callHook(vm, 'mounted');
    }
    return vm
}



//Watcher函数具体实现
var Watcher = function Watcher (
    vm,
    expOrFn,
    cb,
    options,
    isRenderWatcher
  ) {
    
    //...
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn;
    } else {
      this.getter = parsePath(expOrFn);
      if (!this.getter) {
        this.getter = noop;
        warn(
          "Failed watching path: \"" + expOrFn + "\" " +
          'Watcher only accepts simple dot-delimited paths. ' +
          'For full control, use a function instead.',
          vm
        );
      }
    }
    this.value = this.lazy
      ? undefined
      : this.get();
  };


  Watcher.prototype.get = function get () {
    pushTarget(this);
    var value;
    var vm = this.vm;
    try {
     //调用	updateComponent函数
      value = this.getter.call(vm, vm);
    } catch (e) {
      //...
    }
    return value
  };

//vm._update函数具体实现如下:
 function lifecycleMixin (Vue) {
    Vue.prototype._update = function (vnode, hydrating) {
      var vm = this;
      var prevEl = vm.$el;
      var prevVnode = vm._vnode;
      var restoreActiveInstance = setActiveInstance(vm);
      vm._vnode = vnode;
      // Vue.prototype.__patch__ is injected in entry points
      // based on the rendering backend used.
      if (!prevVnode) {
        // initial render
        //关键点初始渲染,vm.__patch__看第三节
        vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
      } else {
        // updates
        vm.$el = vm.__patch__(prevVnode, vnode);
      }
      restoreActiveInstance();
      // update __vue__ reference
      if (prevEl) {
        prevEl.__vue__ = null;
      }
      if (vm.$el) {
        vm.$el.__vue__ = vm;
      }
      // if parent is an HOC, update its $el as well
      if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
        vm.$parent.$el = vm.$el;
      }
      // updated hook is called by the scheduler to ensure that children are
      // updated in a parent's updated hook.
    };

 	//...
  }


//注意这个mount就是上面$mount①
var mount = Vue.prototype.$mount;
//$mount函数实现②如下所示:
Vue.prototype.$mount = function (
   el,
   hydrating
 ) {
   el = el && query(el);
	//...
	//关键点:去函数编译
    var ref = compileToFunctions(template, {
      outputSourceRange: "development" !== 'production',
      shouldDecodeNewlines: shouldDecodeNewlines,
      shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
      delimiters: options.delimiters,
      comments: options.comments
    }, this);
   //...   
   
   //最后通过调用$mount①里面的mountComponent函数
   return mount.call(this, el, hydrating)
 };

//compileToFunctions函数具体实现如下:
function createCompileToFunctionFn (compile) {
    var cache = Object.create(null);

    return function compileToFunctions (
      template,
      options,
      vm
    ) {
      //...

      // 关键点:进行编译compile
      var compiled = compile(template, options);
	  //...

      return (cache[key] = res)
    }
  }

//compile函数具体实现如下所示:
function createCompilerCreator (baseCompile) {
    return function createCompiler (baseOptions) {
      function compile (
        template,
        options
      ) {
       //...
       //关键点
        var compiled = baseCompile(template.trim(), finalOptions);
       //...
      }

      return {
        compile: compile,
        compileToFunctions: createCompileToFunctionFn(compile)
      }
    }
  }

//调用createCompilerCreator函数
var createCompiler = createCompilerCreator(function baseCompile (
    template,
    options
  ) {
    //...
    //关键点
    var code = generate(ast, options);
    //...
  });

//generate函数实现如下所示
  function generate (
    ast,
    options
  ) {
    var state = new CodegenState(options);
    //关键点
    var code = ast ? genElement(ast, state) : '_c("div")';
    return {
      render: ("with(this){return " + code + "}"),
      staticRenderFns: state.staticRenderFns
    }
  }

//genElement函数实现如下所示
function genElement (el, state) {
    //...
    //关键点genChildren
    var children = el.inlineTemplate ? null : genChildren(el, state, true);
     //...
      return code
    }
  }

//genChildren函数具体实现如下:
function genChildren (
    el,
    state,
    checkSkip,
    altGenElement,
    altGenNode
  ) {
    var children = el.children;
    if (children.length) {
      
      var gen = altGenNode || genNode;
      return ("[" + 
      (children.map(function (c) { 
      //关键点
	return gen(c, state); }).join(',')) + "]" + (normalizationType$1 ? ("," + normalizationType$1) : ''))
    }
  }

//这个gen函数调用的具体实现如下:
  function genNode (node, state) {
    if (node.type === 1) {
    //关键点,再次调用genElement
      return genElement(node, state)
    } else if (node.type === 3 && node.isComment) {
      return genComment(node)
    } else {
      return genText(node)
    }
  }

//genElement函数实现如下所示
 function genElement (el, state) {
    if (el.parent) {
      el.pre = el.pre || el.parent.pre;
    }

    //...
      var code;
      if (el.component) {
        code = genComponent(el.component, el, state);
      } else {
        var data;
        if (!el.plain || (el.pre && state.maybeComponent(el))) {
        	//关键点
          data = genData$2(el, state);
        }

        var children = el.inlineTemplate ? null : genChildren(el, state, true);
        code = "_c('" + (el.tag) + "'" + (data ? ("," + data) : '') + (children ? ("," + children) : '') + ")";
      }
      // module transforms
      for (var i = 0; i < state.transforms.length; i++) {
        code = state.transforms[i](el, code);
      }
      return code
  }

//genData$2函数实现如下所示:
function genData$2 (el, state) {
    var data = '{';

    // 处理指令
    var dirs = genDirectives(el, state);
    if (dirs) { data += dirs + ','; }

    //... 指令拼接
    return data
  }

//genDirectives函数具体如下:
 function genDirectives (el, state) {
   		//...
   		//这个会调用到model函数
        needRuntime = !!gen(el, dir, state.warn);
        //...
  }

//model函数具体实现如下:
function model (
    el,
    dir,
    _warn
  ) {
   	//...
    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') {
     //当前demo要走这里
      genDefaultModel(el, value, modifiers);
    } else if (!config.isReservedTag(tag)) {
      genComponentModel(el, value, modifiers);
      // component v-model doesn't need extra runtime
      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.',
        el.rawAttrsMap['v-model']
      );
    }

    // ensure runtime directive metadata
    return true
  }

//genDefaultModel函数如下所示:将具体事件函数拼接成字符串
function genDefaultModel (
    el,
    value,
    modifiers
  ) {
    var type = el.attrsMap.type;

    // warn if v-bind:value conflicts with v-model
    // except for inputs with v-bind:type
    {
      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',
          el.rawAttrsMap[binding]
        );
      }
    }

    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';

    var valueExpression = '$event.target.value';
    if (trim) {
      valueExpression = "$event.target.value.trim()";
    }
    if (number) {
      valueExpression = "_n(" + valueExpression + ")";
    }

    var 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) {
      addHandler(el, 'blur', '$forceUpdate()');
    }
  }


三、vm.__patch__调用顺序

模板编译时期
添加事件时期

  function add$1 (
    name,
    handler,
    capture,
    passive
  ) {
    //...给元素添加事件,在demo中,此处得name为change,handler是就是针对change事件的回调函数
    target$1.addEventListener(
      name,
      handler,
      supportsPassive
        ? { capture: capture, passive: passive }
        : capture
    );
  }

四、总结

v-model底层借助元素的事件机制实现的,通过监听用户针对元素所做的操作以及vue框架内部针对数据的观察机制,从而实现了数据的双向绑定
Logo

前往低代码交流专区

更多推荐