首页 > 作文

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

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

可以用 v-on 指令监听 dom 事件,并在触发时运行一些 javascript 代码,例如:

<!doctype html><html lang="en"><head>    <meta chart="utf-8">    <title>document</title>    <script src="vue.js"></script></head><body>    <div id="app">        <button @click="show('click',$event)" @mouenter="show('mouenter',$event)">测试</button>    </div>    <script>        vue.config.productiontip=fal;        vue.config.devtools=fal;        var app = new vue({            el:'#app',            methods:{ show(type,ev){console.log(type)} }        })    </script></body></html>

渲染结果为:

我们给测试按钮添加了一个mouenter和click事件,鼠标移上去式控制台输出:

当点击时,输出为:

vue的事件绑定有很多种写法,例如:

<!doctype html><html lang="en"><head>    <meta chart="utf-8">    <title>document</title>    <script src="vue.js"></script></head><body>    <div id="app">        <p>{{message}}</p>         <button @click="test1">test1</button>                       <!--事件可以对应一个方法-->        <button @click="test2('test2',$event)">test2</button>          <!--方法还可以传递参数,$event表示原始的dom事件-->        <button @click="message='test3'">test3</button>               <!--也可以是一个表达式-->        <button @click="function(){message='test4'}">test4</button>    <!--也可以是一个函数-->        <button @click="()=>{message='test5'}">test5</button>          <!--也可以是一个箭头函数-->    </div>      <script>        var app = new vue({            el:'#app',            data(){            茱萸少一人    return {message:"hello vue"}            },            methods:{                test1(){console.log('test1');},                test2(text,ev){console.log(text);console.log(ev.type)}            }        })    </script></body></html>

可以看到v-on对应事件可以很多种格式的,可以是当前vue实例的一个方法、一个表达式、一个函数,或者一个箭头函数

源码分析

以上面的第一个例子为例,vue将dom解析成ast对象时的时候执行到a节点时会执行proceslement()函数,然后会执行processattrs()函数,该函数会遍历每个属性,然后用判断是否以:或v-bind:开头,如下:

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)) {                    //bindrd等于/^:|^v-bind:/ ,即该属性是v-bind指令时 例如:<a :href="url">你好</a>        /*这里时v-bind指令对应的分支*/      } el if (onre.test(name)) {               //onre等于/^@|^v-on:/,即该属性是v-on指令时        name = name.replace(onre, '');                              //获取绑定的事件类型  比如@click,此时name等于click   v-on:click此时name也等于click        addhandler(el, name, value, modifiers, fal, warn$2);      //调用addhandler()函数将事件相关信息保存到el.events或nativeevents里面      } el {                                  // normal directives       /*自定义指令的分支*/      }    } el {                                      //存储普通属性的分支      // literal attribute      {        var res = partext(value, delimiters);        if (res) {          warn$2(            name + "=\"" + value + "\": " +            'interpolation inside attributes has been removed. ' +            'u v-bind or the colon shorthand instead. for example, ' +            'instead of <div id="{{ val }}">, u <div :id="val">.'          );        }      }      addattr(el, name, json.stringify(value));      // #6887 firefox doesn't update muted state if t via attribute      // even immediately after element creation      if (!el.component &&          name === 'muted' &&          platformmustuprop(el.tag, el.attrsmap.type, name)) {        addprop(el, name, 'true');      }    }  }}

addhandler()函数用于给对应的ast对象增加一个events属性,保存事件对应的信息,如下:

function addhandler (     //第6573行  给el这个ast对象增加event或nativeevents,用于记录事件的信息   el,  name,  value,  modifiers,  important,  warn) {  modifiers = modifiers || emptyobject;  // warn prevent and passive modifier  /* istanbul ignore if */  if (    "development" !== 'production' && warn &&    modifiers.prevent && modifiers.passive  ) {    warn(      'passive and prevent can\'t be ud together. ' +      'passive handler can\'t prevent default event.'    );  }  // check capture modifier  if (modifiers.capture) {    delete modifiers.capture;    name = '!' + name; // mark the event as captured  }  if (modifiers.once) {             //如果有once修饰符    delete modifiers.once;    name = '~' + name; // mark the event as once  }  /* istanbul ignore if */  if (modifiers.passive) {    delete modifiers.passive;    name = '&' + name; // mark the event as passive  }  // normalize click.right and click.middle since they don't actually fire  // this is technically browr-specific, but at least for now browrs are  // the only target envs that have right/middle clicks.  if (name === 'click') {         //鼠标按键修饰符:如果是click事件,则根据modiflers进行修正    if (modifiers.right) {      name = 'contextmenu';      delete modifiers.right;    } el if (modifiers.middle) {      name = 'mouup';    }  }  var events;  if (modifiers.native) {       //如果存在native修饰符,则保存到el.nativeevents里面,对于组件的自定义事件执行到这里    delete modifiers.native;    events = el.nativeevents || (el.nativeevents = {});  } el {                      //否则保存到el.events里面    events = el.events || (el.events = {});  }  var newhandler = {    value: value.trim()  };  if (modifiers !== emptyobject) {    newhandler.modifiers = modifiers;  }  var handlers = events[name];          //尝试获取已经存在的该事件对象  /* istanbul ignore if */  if (array.isarray(handlers)) {        //如果是数组,表示已经插入了两次了,则再把newhandler添加进去    important ? handlers.unshift(newhandler) : handlers.push(newhandler);  } el if (handlers) {                 //如果handlers存在且不是数组,则表示只插入过一次,则把events[name]变为数组    events[name] = important ? [newhandler, handlers] : [handlers, newhandler];  } el {    events[name] = newhandler;           //否则表示是第一次新增该事件,则值为对应的newhandler  }  el.plain = fal;}

例子里执行到这里这里后对应的ast等于:

接下来在generate生成rendre函数的时候会调用genhandlers函数根据不同修饰符等生成对应的属性(作为_c函数的第二个data参数一部分),

function genhandlers (    //第9992行 拼凑事件的data函数  events,   isnative,  warn) {  var res = isnative ? 'nativeon:{' : 'on:{';     //如果参数isnative为true则设置res为:nativeon:{,否则为:on:{  ;对于组件来说isnative为true,原生事件来说是on  for (var name in events) {                      //遍历events,拼凑结果    res += "\"" + name + "\":" + (genhandler(name, events[name])) + ",";  }  return res.slice(0, -1) + '}'}
如何治疗色斑

genhandler会获取每个事件对应的代码,如下:

function genhandler (   //第10004行  name:事件名,比如:name handler:事件绑定的对象信息,比如:{value: "show", modifiers: {…}}  name,  handler) {  if (!handler) {    return 'function(){}'  }  if (array.isarray(handler)) {       return ("[" + (handler.map(function (handler) { return genhandler(name, handler); }).join(',')) + "]")  }  var ismethodpath = simplepathre.test(handler.value);          //是否为简单的表达式,比如show、show_d、show1等  var isfunctionexpression = fnexpre.test(handler.value);       //是否为函数表达式(箭头函数或function(){}格式的匿名函数)  if (!handler.modifiers) {                                     //如果该事件的修饰符为空    if (ismethodpath || isfunctionexpression) {                     //如果是简单表达式或者是函数表达式      return handler.value                                              //则直接返回handler.value,比如:show    }    /* istanbul ignore if */    return ("function($event){" + (handler.value) + "}") // inline statement  //否则返回带有一个$event变量的函数形式,比如:当value是个表达式时,例如:value=a+123,返回格式:function($event){a+我读书我快乐123;}  } el {                                                      //如果还存在修饰符(解析模板时有些修饰符被过滤掉了)    var code = '';    var genmodifiercode = '';    var keys = [];    for (var key in handler.modifiers) {                          //遍历每个修饰符,比如:prevent      if (modifiercode[key]) {                                        //如果有在modifiercode里面定义   modifiercode是个数组,保存了一些内置修饰符对应的代码        genmodifiercode += modifiercode[key];                            //则拼凑到genmodifiercode里面        // left/right        if (keycodes[key]) {          keys.push(key);        }      } el if (key === 'exact') {        var modifiers = (handler.modifiers);        genmodifiercode += genguard(          ['ctrl', 'shift', 'alt', 'meta']            .filter(function (keymodifier) { return !modifiers[keymodifier]; })            .map(function (keymodifier) { return ("$event." + keymodifier + "key"); })            .join('||')        );      } el 谢谢{        keys.push(key);      }    }    if (keys.length) {                                          //如果有按键      code += genkeyfilter(keys);                                   //则拼凑按键    }       // make sure modifiers like prevent and stop get executed after key filtering    if (genmodifiercode) {      code += genmodifiercode;    }    var handlercode = ismethodpath      ? ("return " + (handler.value) + "($event)")      : isfunctionexpression        ? ("return (" + (handler.value) + ")($event)")        : handler.value;    /* istanbul ignore if */    return ("function($event){" + code + handlercode + "}")  }}

例子里执行到这里后生成的render函数等于:

with(this){return _c(‘div’,{attrs:{“id”:”app”}},[_c(‘button’,{on:{“click”:function($event){show(‘click’,$event)},”mouenter”:function($event){show(‘mouenter’,$event)}}},[_v(“测试”)])])}

其中和事件有关的如下:

on: {    "click": function($event) {        show('click', $event)    },    "mouenter": function($event) {        show('mouenter', $event)    }}

最后在_watch渲染成真实的dom节点后,就会调用events模块的updatedomlisteners钩子函数,该函数会获取该vnode的on属性,依次遍历on对象里的每个元素,最后调用addeventlistener去绑定对应的事件

function updatedomlisteners (oldvnode, vnode) {     //第7083行 domn事件相关  if (isundef(oldvnode.data.on) && isundef(vnode.data.on)) {    return  }  var on = vnode.data.on || {};                                   //新node上的事件  例如:{click: ƒ ($event){}}  var oldon = oldvnode.data.on || {};  target$1 = vnode.elm;                                           //dom引用  normalizeevents(on);                                            //处理v-model的  updatelisteners(on, oldon, add$1, remove$2, vnode.context);     //调用updatelisteners做进一步处理  target$1 = undefined;}

updatelisteners()函数又会调用add$1函数去添加dom事件,如下:

function updatelisteners (      //第2036行 更新dom事件  on,  oldon,  add,  remove$$1,  vm) {  var name, def, cur, old, event;  for (name in on) {                  //遍历on,此时name就是对应的事件类型,比如:click    def = cur = on[name];    old = oldon[name];    event = normalizeevent(name);    /* istanbul ignore if */    if (isundef(cur)) {没有父亲的父亲节发朋友圈怎么说      "development" !== 'production' && warn(        "invalid handler for event \"" + (event.name) + "\": got " + string(cur),        vm      );    } el if (isundef(old)) {                //如果old没有定义,则表示这是一个创建事件      if (isundef(cur.fns)) {        cur = on[name] = createfninvoker(cur);      }      add(event.name, cur, event.once, event.capture, event.passive, event.params);   //调用add()绑定事件    } el if (cur !== old) {      old.fns = cur;      on[name] = old;    }  }  for (name in oldon) {    if (isundef(on[name])) {      event = normalizeevent(name);      remove$$1(event.name, oldon[name], event.capture);    }  }}

updatelisteners里的add函数,也就是全局的add$1函数才是最终的添加事件函数,如下:

function add$1 (    //第7052行  绑定事件  event:事件名 handler:事件的函数 once$$1:是否只执行一次 capture:是否采用捕获状态 passive:可用于移动端性能提升  event,  handler,  once$$1,  capture,  passive) {  handler = withmacrotask(handler);  if (once$$1) { handler = createoncehandler(handler, event, capture); }  //如果有设置了once$$1,则继续使用createoncehandler封装  target$1.addeventlistener(                                              //调用原生的dom apiaddeventlistener添加对应的事件,2017年dom规范对addeventlistener()的第三个参数做了修订,可以是一个对象    event,    handler,    supportspassive      ? { capture: capture, passive: passive }      : capture  );}

我们看到vue内部添加dom事件最终也是通过addeventlistener()来添加的,说到底,vue只是把这些api进行了封装,使我们用起来更方便而已。

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

本文链接:https://www.wtabcd.cn/fanwen/zuowen/64d31a9e00b6ab282e8adc5ceff22834.html

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

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

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

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