经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » HTML/CSS » HTML5 » 查看文章
Vue.js 源码分析(十六) 指令篇 v-on指令详解
来源:cnblogs  作者:大沙漠  时间:2019/7/3 8:54:00  对本文有异议

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

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Document</title>
  6. <script src="vue.js"></script>
  7. </head>
  8. <body>
  9. <div id="app">
  10. <button @click="show('click',$event)" @mouseenter="show('mouseenter',$event)">测试</button>
  11. </div>
  12. <script>
  13. Vue.config.productionTip=false;
  14. Vue.config.devtools=false;
  15. var app = new Vue({
  16. el:'#app',
  17. methods:{ show(type,ev){console.log(type)} }
  18. })
  19. </script>
  20. </body>
  21. </html>

渲染结果为:

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

当点击时,输出为:

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

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Document</title>
  6. <script src="vue.js"></script>
  7. </head>
  8. <body>
  9. <div id="app">
  10. <p>{{message}}</p>
  11. <button @click="test1">Test1</button>     <!--事件可以对应一个方法-->
  12. <button @click="test2('test2',$event)">Test2</button> <!--方法还可以传递参数,$event表示原始的DOM事件-->
  13. <button @click="message='test3'">Test3</button>   <!--也可以是一个表达式-->
  14. <button @click="function(){message='test4'}">Test4</button> <!--也可以是一个函数-->
  15. <button @click="()=>{message='test5'}">Test5</button> <!--也可以是一个箭头函数-->
  16. </div>
  17. <script>
  18. var App = new Vue({
  19. el:'#app',
  20. data(){
  21. return {message:"Hello Vue"}
  22. },
  23. methods:{
  24. test1(){console.log('test1');},
  25. test2(text,ev){console.log(text);console.log(ev.type)}
  26. }
  27. })
  28. </script>
  29. </body>
  30. </html>

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

 

 源码分析


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

  1. function processAttrs (el) { //第9526行 对剩余的属性进行分析
  2. var list = el.attrsList;
  3. var i, l, name, rawName, value, modifiers, isProp;
  4. for (i = 0, l = list.length; i < l; i++) { //遍历每个属性
  5. name = rawName = list[i].name; //获取属性名
  6. value = list[i].value; //该属性对应的值
  7. if (dirRE.test(name)) { //如果该属性以v-、@或:开头,表示这是Vue内部指令
  8. // mark element as dynamic
  9. el.hasBindings = true;
  10. // modifiers
  11. modifiers = parseModifiers(name);
  12. if (modifiers) {
  13. name = name.replace(modifierRE, '');
  14. }
  15. if (bindRE.test(name)) { //bindRD等于/^:|^v-bind:/ ,即该属性是v-bind指令时 例如:<a :href="url">你好</a>
  16. /*这里时v-bind指令对应的分支*/
  17. } else if (onRE.test(name)) { //onRE等于/^@|^v-on:/,即该属性是v-on指令时
  18. name = name.replace(onRE, ''); //获取绑定的事件类型 比如@click,此时name等于click v-on:click此时name也等于click
  19. addHandler(el, name, value, modifiers, false, warn$2); //调用addHandler()函数将事件相关信息保存到el.events或nativeEvents里面
  20. } else { // normal directives
  21. /*自定义指令的分支*/
  22. }
  23. } else { //存储普通属性的分支
  24. // literal attribute
  25. {
  26. var res = parseText(value, delimiters);
  27. if (res) {
  28. warn$2(
  29. name + "=\"" + value + "\": " +
  30. 'Interpolation inside attributes has been removed. ' +
  31. 'Use v-bind or the colon shorthand instead. For example, ' +
  32. 'instead of <div id="{{ val }}">, use <div :id="val">.'
  33. );
  34. }
  35. }
  36. addAttr(el, name, JSON.stringify(value));
  37. // #6887 firefox doesn't update muted state if set via attribute
  38. // even immediately after element creation
  39. if (!el.component &&
  40. name === 'muted' &&
  41. platformMustUseProp(el.tag, el.attrsMap.type, name)) {
  42. addProp(el, name, 'true');
  43. }
  44. }
  45. }
  46. }

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

  1. function addHandler ( //第6573行 给el这个AST对象增加event或nativeEvents,用于记录事件的信息
  2. el,
  3. name,
  4. value,
  5. modifiers,
  6. important,
  7. warn
  8. ) {
  9. modifiers = modifiers || emptyObject;
  10. // warn prevent and passive modifier
  11. /* istanbul ignore if */
  12. if (
  13. "development" !== 'production' && warn &&
  14. modifiers.prevent && modifiers.passive
  15. ) {
  16. warn(
  17. 'passive and prevent can\'t be used together. ' +
  18. 'Passive handler can\'t prevent default event.'
  19. );
  20. }
  21. // check capture modifier
  22. if (modifiers.capture) {
  23. delete modifiers.capture;
  24. name = '!' + name; // mark the event as captured
  25. }
  26. if (modifiers.once) { //如果有once修饰符
  27. delete modifiers.once;
  28. name = '~' + name; // mark the event as once
  29. }
  30. /* istanbul ignore if */
  31. if (modifiers.passive) {
  32. delete modifiers.passive;
  33. name = '&' + name; // mark the event as passive
  34. }
  35. // normalize click.right and click.middle since they don't actually fire
  36. // this is technically browser-specific, but at least for now browsers are
  37. // the only target envs that have right/middle clicks.
  38. if (name === 'click') { //鼠标按键修饰符:如果是click事件,则根据modiflers进行修正
  39. if (modifiers.right) {
  40. name = 'contextmenu';
  41. delete modifiers.right;
  42. } else if (modifiers.middle) {
  43. name = 'mouseup';
  44. }
  45. }
  46. var events;
  47. if (modifiers.native) { //如果存在native修饰符,则保存到el.nativeEvents里面,对于组件的自定义事件执行到这里
  48. delete modifiers.native;
  49. events = el.nativeEvents || (el.nativeEvents = {});
  50. } else { //否则保存到el.events里面
  51. events = el.events || (el.events = {});
  52. }
  53. var newHandler = {
  54. value: value.trim()
  55. };
  56. if (modifiers !== emptyObject) {
  57. newHandler.modifiers = modifiers;
  58. }
  59. var handlers = events[name]; //尝试获取已经存在的该事件对象
  60. /* istanbul ignore if */
  61. if (Array.isArray(handlers)) { //如果是数组,表示已经插入了两次了,则再把newHandler添加进去
  62. important ? handlers.unshift(newHandler) : handlers.push(newHandler);
  63. } else if (handlers) { //如果handlers存在且不是数组,则表示只插入过一次,则把events[name]变为数组
  64. events[name] = important ? [newHandler, handlers] : [handlers, newHandler];
  65. } else {
  66. events[name] = newHandler; //否则表示是第一次新增该事件,则值为对应的newHandler
  67. }
  68. el.plain = false;
  69. }

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

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

 

  1. function genHandlers ( //第9992行 拼凑事件的data函数
  2. events,
  3. isNative,
  4. warn
  5. ) {
  6. var res = isNative ? 'nativeOn:{' : 'on:{'; //如果参数isNative为true则设置res为:nativeOn:{,否则为:on:{ ;对于组件来说isNative为true,原生事件来说是on
  7. for (var name in events) { //遍历events,拼凑结果
  8. res += "\"" + name + "\":" + (genHandler(name, events[name])) + ",";
  9. }
  10. return res.slice(0, -1) + '}'
  11. }

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

  1. function genHandler ( //第10004行 name:事件名,比如:name handler:事件绑定的对象信息,比如:{value: "show", modifiers: {…}}
  2. name,
  3. handler
  4. ) {
  5. if (!handler) {
  6. return 'function(){}'
  7. }
  8. if (Array.isArray(handler)) {
  9. return ("[" + (handler.map(function (handler) { return genHandler(name, handler); }).join(',')) + "]")
  10. }
  11. var isMethodPath = simplePathRE.test(handler.value); //是否为简单的表达式,比如show、show_d、show1等
  12. var isFunctionExpression = fnExpRE.test(handler.value); //是否为函数表达式(箭头函数或function(){}格式的匿名函数)
  13.  
  14. if (!handler.modifiers) { //如果该事件的修饰符为空
  15. if (isMethodPath || isFunctionExpression) { //如果是简单表达式或者是函数表达式
  16. return handler.value //则直接返回handler.value,比如:show
  17. }
  18. /* istanbul ignore if */
  19. return ("function($event){" + (handler.value) + "}") // inline statement //否则返回带有一个$event变量的函数形式,比如:当value是个表达式时,例如:value=a+123,返回格式:function($event){a+123;}
  20. } else { //如果还存在修饰符(解析模板时有些修饰符被过滤掉了)
  21. var code = '';
  22. var genModifierCode = '';
  23. var keys = [];
  24. for (var key in handler.modifiers) { //遍历每个修饰符,比如:prevent
  25. if (modifierCode[key]) { //如果有在modifierCode里面定义 modifierCode是个数组,保存了一些内置修饰符对应的代码
  26. genModifierCode += modifierCode[key]; //则拼凑到genModifierCode里面
  27. // left/right
  28. if (keyCodes[key]) {
  29. keys.push(key);
  30. }
  31. } else if (key === 'exact') {
  32. var modifiers = (handler.modifiers);
  33. genModifierCode += genGuard(
  34. ['ctrl', 'shift', 'alt', 'meta']
  35. .filter(function (keyModifier) { return !modifiers[keyModifier]; })
  36. .map(function (keyModifier) { return ("$event." + keyModifier + "Key"); })
  37. .join('||')
  38. );
  39. } else {
  40. keys.push(key);
  41. }
  42. }
  43. if (keys.length) { //如果有按键
  44. code += genKeyFilter(keys); //则拼凑按键
  45. }
  46. // Make sure modifiers like prevent and stop get executed after key filtering
  47. if (genModifierCode) {
  48. code += genModifierCode;
  49. }
  50. var handlerCode = isMethodPath
  51. ? ("return " + (handler.value) + "($event)")
  52. : isFunctionExpression
  53. ? ("return (" + (handler.value) + ")($event)")
  54. : handler.value;
  55. /* istanbul ignore if */
  56. return ("function($event){" + code + handlerCode + "}")
  57. }
  58. }

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

with(this){return _c('div',{attrs:{"id":"app"}},[_c('button',{on:{"click":function($event){show('click',$event)},"mouseenter":function($event){show('mouseenter',$event)}}},[_v("测试")])])}

其中和事件有关的如下:

  1. on: {
  2. "click": function($event) {
  3. show('click', $event)
  4. },
  5. "mouseenter": function($event) {
  6. show('mouseenter', $event)
  7. }
  8. }

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

 

  1. function updateDOMListeners (oldVnode, vnode) { //第7083行 DOMN事件相关
  2. if (isUndef(oldVnode.data.on) && isUndef(vnode.data.on)) {
  3. return
  4. }
  5. var on = vnode.data.on || {}; //新Node上的事件 例如:{click: ƒ ($event){}}
  6. var oldOn = oldVnode.data.on || {};
  7. target$1 = vnode.elm; //DOM引用
  8. normalizeEvents(on); //处理V-model的
  9. updateListeners(on, oldOn, add$1, remove$2, vnode.context); //调用updateListeners做进一步处理
  10. target$1 = undefined;
  11. }

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

  1. function updateListeners ( //第2036行 更新DOM事件
  2. on,
  3. oldOn,
  4. add,
  5. remove$$1,
  6. vm
  7. ) {
  8. var name, def, cur, old, event;
  9. for (name in on) { //遍历on,此时name就是对应的事件类型,比如:click
  10. def = cur = on[name];
  11. old = oldOn[name];
  12. event = normalizeEvent(name);
  13. /* istanbul ignore if */
  14. if (isUndef(cur)) {
  15. "development" !== 'production' && warn(
  16. "Invalid handler for event \"" + (event.name) + "\": got " + String(cur),
  17. vm
  18. );
  19. } else if (isUndef(old)) { //如果old没有定义,则表示这是一个创建事件
  20. if (isUndef(cur.fns)) {
  21. cur = on[name] = createFnInvoker(cur);
  22. }
  23. add(event.name, cur, event.once, event.capture, event.passive, event.params); //调用add()绑定事件
  24. } else if (cur !== old) {
  25. old.fns = cur;
  26. on[name] = old;
  27. }
  28. }
  29. for (name in oldOn) {
  30. if (isUndef(on[name])) {
  31. event = normalizeEvent(name);
  32. remove$$1(event.name, oldOn[name], event.capture);
  33. }
  34. }
  35. }

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

  1. function add$1 ( //第7052行 绑定事件 event:事件名 handler:事件的函数 once$$1:是否只执行一次 capture:是否采用捕获状态 passive:可用于移动端性能提升
  2. event,
  3. handler,
  4. once$$1,
  5. capture,
  6. passive
  7. ) {
  8. handler = withMacroTask(handler);
  9. if (once$$1) { handler = createOnceHandler(handler, event, capture); } //如果有设置了once$$1,则继续使用createOnceHandler封装
  10. target$1.addEventListener( //调用原生的DOM APIaddEventListener添加对应的事件,2017年DOM规范对addEventListener()的第三个参数做了修订,可以是一个对象
  11. event,
  12. handler,
  13. supportsPassive
  14. ? { capture: capture, passive: passive }
  15. : capture
  16. );
  17. }

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

原文链接:http://www.cnblogs.com/greatdesert/p/11121952.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号