首页 > 作文

Vue.js 源码分析(二十二) 指令篇 v

更新时间:2023-04-03 03:02:55 阅读: 评论:0

vue.js提供了v-model指令用于双向数据绑定,比如在输入框上使用时,输入的内容会事实映射到绑定的数据上,绑定的数据又可以显示在页面里,数据显示的过程是自动完成的。

v-model本质上不过是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。例如:

<!doctype html><html lang="en"><head>    <meta chart="utf-8">    <title>document</title>    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script></head><body>    <div id="app">        <p>message is: {{message}}</p>        <input v-model="message" placeholder="edit me" type="text">    </div>    <script>        vue.config.productiontip=fal;        vue.config.devtools=fal;        new vue({el: '#app',data(){return { message:'' }}})    </script></body></html>

渲染如下:

当我们在输入框输入内容时,message is:后面会自动显示输入框里的内容,反过来当修改vue实例的message时,输入框也会自动更新为该内容。

与事件的修饰符类似,v-model也有修饰符,用于控制数据同步的时机,v-model可以添加三个修饰符:lazy、number和trim,具体可以看官网。

我们如果不用v-model,手写一些事件也可以实现例子里的效果,如下:

<!doctype html><html lang="en"><head>    <meta chart="utf-8">    <title>document</title>    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script></head><body>    <div id="app">        <p>message is: {{message}}</p>        <input :value="message" @input="message=$event.target.value" placeholder="edit me" type="text">    </div>    <script>        vue.config.productiontip=fal;        vue.config.devtools=fal;        new vue({el: '#app',data(){return { message:'' }}})    </script></body></html>

我们自己手写的和用v-model有一点不同,就是当输入中文时,输入了拼音,但是没有按回车时,p标签也会显示message信息的,而用v-model实现的双向绑定是只有等到回车按下去了才会渲染的,这是因为v-model内部监听了compositionstart和compositionend事件,有兴趣的同学具体可以查看一下这两个事件的用法,网上教程挺多的。

源码分析

vue是可以自定义指令的,其中v-model和v-show是vue的内置指令,它的写法和我们的自定义指令是一样的,都保存到vue.options.directives上,例如:

<!doctype html><html lang="en"><head>    <meta chart="utf-8">    <title>document</title>    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script></head><body>    <script>        console.log(vue.options.directives)     //打印vue.options.directives的值    </script></body></html>

输出如下:

vue内部通过extend(vue.options.directives, platformdirectives); 将v-model和v-show的指令信息保存到vue.options.directives里面,如下:

var platformdirectives = {                           //第8417行  内置指令 v-module和v-show  platformdirectives的意思是这两个指令和平台无关的,不管任何环境都可以用这两个指令  model: directive,  show: show}extend(vue.options.directives, platformdirectives); //第8515行 将两个指令信息保存到vue.options.directives里面

vue的源码实现代码比较多,我们一步步来,以上面的第一个例子为例,当vue将模板解析成ast对象解析到input时会processattrs()函数,如下:

function processattrs (el) {            //第9526行 对剩余的属性进行分析  var list = el.attrslist;  var i, l, name, rawname, value, modifiers, isprop;  for (i = 0, l = list.length; i < l; i++) {      //遍历每个属性    name = rawname = list[i].name;    value = list[i].value;    if (dirre.test(name)) {                         //如果该属性以v-、@或:开头,表示这是vue内部指令      // mark element as dynamic      el.hasbindings = true;      // modifiers      modifiers = parmodifiers(name);      if (modifiers) {        name = name.replace(modifierre, '');      }      if (bindre.test(name)) { // v-bind              //bindrd等于/^:|^v-bind:/ ,即该属性是v-bind指令时       /*v-bind逻辑*/      } el if (onre.test(name)) { // v-on           //onre等于/^@|^v-on:/,即该属性是v-on指令时        /*v-on逻辑*/      } el { // normal directives                   //普通指令        name = name.replace(dirre, '');                   //去掉指令前缀,比如v-model执行后等于model        // par arg        var argmatch = name.match(argre);                 //argre等于:(.*)$/,如果name以:开头的话        var arg = argmatch && argmatch[1];        if (arg) {          name = name.slice(0, -(arg.length + 1));        }        adddirective(el, name, rawname, value, arg, modifiers);   //执行adddirective给el增加一个directives属性,值是一个数组,例如:[{name: "model", rawname: "v-model", value: "message", arg: null, modifiers: undefined}]        if ("development" !== 'production' && name === 'model') {          checkforaliasmodel(el, value);        }      }    } el {      /*普通特性的逻辑*/    }  }}

adddirective会给ast对象增加一个directives属性,用于保存对应的指令信息,如下:

function adddirective (     //第6561行 指令相关,给el这个ast对象增加一个directives属性,值为该指令的信息,比如:  el,   name,  rawname,  value,  arg,  modifiers) {  (el.directives || (el.directives = [])).push({ name: name, rawname: rawname, value: value, arg: arg, modifiers: modifiers });  el.plain = fal;}

例子里的 <input v-model=”message” placeholder=”edit me” type=”text”>对应的ast对象如下:

接下来在generate生成rendre函数的时候,获取data属性时会执行gendirectives()函数,该函数会执行全局的model函数,也就是v-model的初始化函简单回答数,如下:

function gendirectives (el, state) {        //第10352行 获取指令  var dirs = el.directives;                   //获取元素的directives属性,是个数组,例如:[{name: "model", rawname: "v-model", value: "message", arg: null, modifiers: undefined}]  if (!dirs) { return }                       //如果没有directives则直接返回  var res = 'directives:[';  var hasruntime = fal;  var i, l, dir, needruntime;  for (i = 0, l = dirs.length; i < l; i++) {        //遍历dirs    dir = dirs[i];                                  //每一个directive,例如:{name: "model", rawname: "v-炒菜大全model", value: "message", arg: null, modifier一元三次方程因式分解s: undefined}    needruntime = true;    var g芳的成语en = state.directives[dir.name];           //获取对应的指令函数,如果是v-model,则对应model函数,可能为空的,只有内部指令才有    if (gen) {      // compile-time directive that manipulates ast.      // returns true if it also needs a runtime counterpart.      needruntime = !!gen(el, dir, state.warn);       //执行指令对应的函数,也就是全局的model函数    }    if (needruntime) {      hasruntime = true;      res += "{name:\"" + (dir.name) + "\",rawname:\"" + (dir.rawname) + "\"" + (dir.value ? (",value:(" + (dir.value) + "),expression:" + (json.stringify(dir.value))) : '') + (dir.arg ? (",arg:\"" + (dir.arg) + "\"") : '') + (dir.modifiers ? (",modifiers:" + (json.stringify(dir.modifiers))) : '') + "},";    }  }  if (hasruntime) {     return res.slice(0, -1) + ']'                 //去掉最后的逗号,并加一个],最后返回  }}

model()函数会根据不同的tag(lect、input的不同)做不同的处理,如下:

function model (      //第6854行 v-model指令的初始化  el,  dir,  _warn) {     warn$1 = _warn;  var value = dir.value;                                        //值  var modifiers = dir.modifiers;                                //修饰符  var tag = el.tag;                                             //标签名,比如:input  var type = el.attrsmap.type;  {    // inputs with type="file" are read only and tting the input's    // value will throw an error.    if (tag === 'input' && type === 'file') {      warn$1(        "<" + (el.tag) + " v-model=\"" + value + "\" type=\"file\">:\n" +        "file inputs are read only. u a v-on:change listener instead."      );    }  }  if (el.component) {    gencomponentmodel(el, value, modifiers);    // component v-model doesn't 情人节送女孩什么礼物need extra runtime    return fal  } el if (tag === 'lect') {                              //如果typ为lect下拉类型    genlect(el, value, modifiers);  } el if (tag === 'input' && type === 'checkbox') {    gencheckboxmodel(el, value, modifiers);  } el if (tag === 'input' && type === 'radio') {    genradiomodel(el, value, modifiers);  } el if (tag === 'input' || tag === 'textarea') {         //如果是input标签,或者是textarea标签    gendefaultmodel(el, value, modifiers);             //则执行gendefaultmodel()函数  } el if (!config.isrervedtag(tag)) {    gencomponentmodel(el, value, modifiers);    // component v-model doesn't need extra runtime    return fal  } el {    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 purpo inside a custom component.'    );  }  // ensure runtime directive metadata  return true}

gendefaultmodel会在el的value绑定对应的值,并调用addhandler()添加对应的事件,如下:

function gendefaultmodel (          //第6965行  nput标签 和textarea标签 el:ast对象 value:对应值  el,  value,  modifiers) {  var type = el.attrsmap.type;                                  //获取type值,比如text,如果未指定则为undefined  // 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'];           //尝试获取动态绑定的value值    var typebinding = el.attrsmap['v-bind:type'] || el.attrsmap[':type'];         //尝试获取动态绑定的type值    if (value$1 && !typebinding) {                                                //如果动态绑定了value 且没有绑定type,则报错      var binding = el.attrsmap['v-bind:value'] ? 'v-bind:value' : ':value';                        warn$1(        binding + "=\"" + value$1 + "\" conflicts with v-model on the same element " +        'becau the latter already expands to a value binding internally'      );    }  }  var ref = modifiers || {};   var lazy = ref.lazy;                                                        //获取lazy修饰符  var number = ref.number;                                                    //获取number修饰符  var trim = ref.trim;                                                        //获取trim修饰符  var needcompositionguard = !lazy && type !== 'range';  var event = lazy                                                            //如果有lazy修饰符则绑定为change事件,否则绑定input事件    ? 'change'    : type === 'range'      ? range_token      : 'input';  var valueexpression = '$event.target.value';  if (trim) {                                                                 //如果有trim修饰符,则在值后面加上trim()    valueexpression = "$event.target.value.trim()";  }  if (number) {                                                               //如果有number修饰符,则加上_n函数,就是全局的tonumber函数    valueexpression = "_n(" + valueexpression + ")";  }  var code = genassignmentcode(value, valueexpression);                       //返回一个表达式,例如:message=$event.target.value  if (needcompositionguard) {                                                 //如果需要composing配合,则在前面加上一段if语句    code = "if($event.target.composing)return;" + code;  }   //双向绑定就是靠着两行代码的  addprop(el, 'value', ("(" + value + ")"));                                  //添加一个value的prop  addhandler(el, event, code, null, true);                                    //添加event事件  if (trim || number) {                                                 addhandler(el, 'blur', '$forceupdate()');  }}

渲染完成后对应的render函数如下:

with(this){return _c('div',{attrs:{"id":"app"}},[_c('p',[_v("message is: "+_s(message))]),_v(" "),_c('input',{directives:[{name:"model",rawname:"v-model",value:(message),expression:"message"}],attrs:{"placeholder":"edit me","type":"text"},domprops:{"value":(message)},on:{"input":function($event){if($event.target.composing)return;message=$event.target.value}}})])}

我们整理一下就看得清楚一点,如下:

with(this) {    return _c('div', {        attrs: {            "id": "app"        }    },    [_c('p', [_v("message is: " + _s(message))]), _v(" "), _c('input', {        directives: [{            name: "model",            rawname: "v-model",            value: (message),            expression: "message"        }],        attrs: {            "placeholder": "edit me",            "type": "text"        },        domprops: {            "value": (message)        },        on: {            "input": function($event) {                if ($event.target.composing) return;                message = $event.target.value            }        }    })])}

最后等dom节点渲染成功后就会执行events模块的初始化事件 并且会执行directive模块的inrted钩子函数:

var directive = {  inrted: function inrted (el, binding, vnode, oldvnode) {      //第7951行    if (vnode.tag === 'lect') {      // #6903      if (oldvnode.elm && !oldvnode.elm._voptions) {        mergevnodehook(vnode, 'postpatch', function () {          directive.componentupdated(el, binding, vnode);        });      } el {        tlected(el, binding, vnode.context);      }      el._voptions = [].map.call(el.options, getvalue);    } el if (vnode.tag === 'textarea' || istextinputtype(el.type)) {      //如果tag是textarea节点,或者type为这些之一:text,number,password,arch,email,tel,url      el._vmodifiers = binding.modifiers;                                       //保存修饰符      if (!binding.modifiers.lazy) {                                            //如果没有lazy修饰符,先后绑定三个事件        el.addeventlistener('compositionstart', oncompositionstart);        el.addeventlistener('compositionend', oncompositionend);        // safari < 10.2 & uiwebview doesn't fire compositionend when        // switching focus before confirming composition choice        // this also fixes the issue where some browrs e.g. ios chrome        // fires "change" instead of "input" on autocomplete.        el.addeventlistener('change', oncompositionend);        /* istanbul ignore if */        if (isie9) {          el.vmodel = true;        }      }    }  },

oncompositionstart和oncompositionend分别对应compositionstart和compositionend事件,如下:

function oncompositionstart (e) {       //第8056行  e.target.composing = true;}function oncompositionend (e) {  // prevent triggering an input event for no reason  if (!e.target.composing) { return }   //如果e.target.composing为fal,则直接返回,即保证不会重复触发  e.target.composing = fal;  trigger(e.target, 'input');               //触发e.target的input事件}function trigger (el, type) {           //触发el上的type事件 例如type等于:input  var e = document.createevent('htmlevents');   //创建一个htmlevents类型  e.initevent(type, true, true);                //初始化事件  el.dispatchevent(e);                           //向el这个元素派发e这个事件}

最后执行的el.dispatchevent(e)就会触发我们生成的render函数上定义的input事件

本文发布于:2023-04-03 03:02:51,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/zuowen/013212feeeedceb2db9fbe0097f52293.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

本文word下载地址:Vue.js 源码分析(二十二) 指令篇 v.doc

本文 PDF 下载地址:Vue.js 源码分析(二十二) 指令篇 v.pdf

标签:指令   函数   事件   绑定
相关文章
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图