您好,欢迎来到三六零分类信息网!老站,搜索引擎当天收录,欢迎发信息

如何使用vue源码解析事件机制

2024/4/26 12:27:18发布21次查看
这次给大家带来如何使用vue源码解析事件机制,使用vue源码解析事件机制的注意事项有哪些,下面就是实战案例,一起来看一下。
<p id="app">  <p id="test1" @click="click1">click1</p>  <p id="test2" @click.stop="click2">click2</p>  <my-component v-on:click.native="nativeclick" v-on:componenton="parenton">  </my-component> </p> </body> <script src="vue.js"></script> <script type="text/javascript"> var child = {  template: '<p>a custom component!</p>' }  vue.component('my-component', {  name: 'my-component',  template: '<p>a custom component!<p @click.stop="toparent">test click</p></p>',  components: {  child:child  },  created(){  console.log(this);  },  methods: {  toparent(){   this.$emit('componenton','toparent')  }  },  mounted(){  console.log(this);  } })  new vue({  el: '#app',  data: function () {  return {   heihei:{name:3333},   a:1  }  },  components: {  child:child  },  methods: {  click1(){   alert('click1')  },  click2(){   alert('click2')  },  nativeclick(){   alert('nativeclick')  },  parenton(value){   alert(value)  }  } }) </script>
上面的demo中一共有四个事件。基本涵盖了vue中最经典的事件的四种情况
普通html元素上的事件
好吧。想想我们还是一个个来看。如果懂vue组件相关的机制会更容易懂。那么首先我们看看最简单的第一、二个(两个事件只差了个修饰符):
<p id="test1" @click="click1">click1</p>
这是简单到不能在简单的一个点击事件。
我们来看看建立这么一个简单的点击事件,vue中发生了什么。
1:new vue()中调用了initstate(vue):看代码
function initstate (vm) {  vm._watchers = [];  var opts = vm.$options;  if (opts.props) { initprops(vm, opts.props); }  if (opts.methods) { initmethods(vm, opts.methods); }//初始化事件  if (opts.data) {  initdata(vm);  } else {  observe(vm._data = {}, true /* asrootdata */);  }  if (opts.computed) { initcomputed(vm, opts.computed); }  if (opts.watch) { initwatch(vm, opts.watch); } } //接着看看initmethods function initmethods (vm, methods) {  var props = vm.$options.props;  for (var key in methods) {  vm[key] = methods[key] == null ? noop : bind(methods[key], vm);//调用了bind方法,我们再看看bind  {   if (methods[key] == null) {   warn(    method \ + key + \ has an undefined value in the component definition.  +    did you reference the function correctly?,    vm   );   }   if (props && hasown(props, key)) {   warn(    (method \ + key + \ has already been defined as a prop.),    vm   );   }  }  } } //我们接着看看bind function bind (fn, ctx) {  function boundfn (a) {  var l = arguments.length;  return l   ? l > 1   ? fn.apply(ctx, arguments)//通过返回函数修饰了事件的回调函数。绑定了事件回调函数的this。并且让参数自定义。更加的灵活   : fn.call(ctx, a)   : fn.call(ctx)  }  // record original fn length  boundfn._length = fn.length;  return boundfn }
总的来说。vue初始化的时候,将method中的方法代理到vue[key]的同时修饰了事件的回调函数。绑定了作用域。
2:vue进入compile环节需要将该p变成ast(抽象语法树)。当编译到该p时经过核心函数genhandler:
function genhandler (  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);  var isfunctionexpression = fnexpre.test(handler.value);  if (!handler.modifiers) {  return ismethodpath || isfunctionexpression//假如没有修饰符。直接返回回调函数   ? handler.value   : (function($event){ + (handler.value) + }) // inline statement  } else {  var code = '';  var genmodifiercode = '';  var keys = [];  for (var key in handler.modifiers) {   if (modifiercode[key]) {   genmodifiercode += modifiercode[key];//处理修饰符数组,例如.stop就在回调函数里加入event.stoppropagation()再返回。实现修饰的目的   // left/right   if (keycodes[key]) {    keys.push(key);   }   } else {   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   ? handler.value + '($event)'   : isfunctionexpression   ? (( + (handler.value) + )($event))   : handler.value;  return (function($event){ + code + handlercode + })  } }
genhandler函数简单明了,如果事件函数有修饰符。就处理完修饰符,添加修饰符对应的函数语句。再返回。这个过程还会单独对native修饰符做特殊处理。这个等会说。compile完后自然就render。我们看看render函数中这块区域长什么样子:
复制代码 代码如下:
_c('p',{attrs:{id:test1},on:{click:click1}},[_v(click1)]),_v( ),_c('p',{attrs:{id:test2},on:{click:function($event){$event.stoppropagation();click2($event)}}}
一目了然。最后在虚拟dom-》真实dom的时候。会调用核心函数:
function add$1 (  event,  handler,  once$$1,  capture,  passive ) {  if (once$$1) {  var oldhandler = handler;  var _target = target$1; // save current target element in closure  handler = function (ev) {   var res = arguments.length === 1   ? oldhandler(ev)   : oldhandler.apply(null, arguments);   if (res !== null) {   remove$2(event, handler, capture, _target);   }  };  }  target$1.addeventlistener(  event,  handler,  supportspassive   ? { capture: capture, passive: passive }//此处绑定点击事件   : capture  ); }
组件上的事件
好了下面就是接下来的组件上的点击事件了。可以预感到他走的和普通的html元素应该是不同的道路。事实也是如此:
<my-component v-on:click.native="nativeclick" v-on:componenton="parenton">  </my-component>
最简单的一个例子。两个事件的区别就是一个有.native的修饰符。我们来看看官方.native的作用:在原生dom上绑定事件。好吧。很简单。我们跟随源码看看有何不同。这里可以往回看看我少的可怜的上一章组件机制。vue中的组件都是扩展的vue的一个新实例。在compile结束的时候你还是可以发现他也是类似的一个样子。如下图:
复制代码 代码如下:
_c('my-component',{on:{componenton:parenton},nativeon:{click:function($event){nativeclick($event)}}
可以看到加了.native修饰符的会被放入nativeon的数组中。等待后续特殊处理。等不及了。我们直接来看看特殊处理。render函数在执行时。如果遇到组件。看过上一章的可以知道。会执行
function createcomponent (  ctor,  data,  context,  children,  tag ) {  if (isundef(ctor)) {  return  }  var basector = context.$options._base;  // plain options object: turn it into a constructor  if (isobject(ctor)) {  ctor = basector.extend(ctor);  }  // if at this stage it's not a constructor or an async component factory,  // reject.  if (typeof ctor !== 'function') {  {   warn((invalid component definition:  + (string(ctor))), context);  }  return  }  // async component  if (isundef(ctor.cid)) {  ctor = resolveasynccomponent(ctor, basector, context);  if (ctor === undefined) {   // return nothing if this is indeed an async component   // wait for the callback to trigger parent update.   return  }  }  // resolve constructor options in case global mixins are applied after  // component constructor creation  resolveconstructoroptions(ctor);  data = data || {};  // transform component v-model data into props & events  if (isdef(data.model)) {  transformmodel(ctor.options, data);  }  // extract props  var propsdata = extractpropsfromvnodedata(data, ctor, tag);  // functional component  if (istrue(ctor.options.functional)) {  return createfunctionalcomponent(ctor, propsdata, data, context, children)  }  // extract listeners, since these needs to be treated as  // child component listeners instead of dom listeners  var listeners = data.on;//listeners缓存data.on的函数。这里就是componenton事件  // replace with listeners with .native modifier  data.on = data.nativeon;//正常的data.on会被native修饰符的事件所替换  if (istrue(ctor.options.abstract)) {  // abstract components do not keep anything  // other than props & listeners  data = {};  }  // merge component management hooks onto the placeholder node  mergehooks(data);  // return a placeholder vnode  var name = ctor.options.name || tag;  var vnode = new vnode(  (vue-component- + (ctor.cid) + (name ? (- + name) : '')),  data, undefined, undefined, undefined, context,  { ctor: ctor, propsdata: propsdata, listeners: listeners, tag: tag, children: children }  );  return vnode }
整段代码关于事件核心操作:
var listeners = data.on;//listeners缓存data.on的函数。这里就是componenton事件 // replace with listeners with .native modifier data.on = data.nativeon;//正常的data.on会被native修饰符的事件所替换
经过这两句话。.native修饰符的事件会被放在data.on上面。接下来data.on上的事件(这里就是nativeclick)会按普通的html事件往下走。最后执行target.add('',''')挂上原生的事件。而先前的data.on上的被缓存在listeneners的事件就没着么愉快了。接下来他会在组件init的时候。它会进入一下分支:
function initevents (vm) {  vm._events = object.create(null);  vm._hashookevent = false;  // init parent attached events  var listeners = vm.$options._parentlisteners;  if (listeners) {  updatecomponentlisteners(vm, listeners);  } } function updatecomponentlisteners (  vm,  listeners,  oldlisteners ) {  target = vm;  updatelisteners(listeners, oldlisteners || {}, add, remove$1, vm); } function add (event, fn, once$$1) {  if (once$$1) {  target.$once(event, fn);  } else {  target.$on(event, fn);  } }
发现组件上的没有.native的修饰符调用的是$on方法。这个好熟悉。进入到$on,$emit大致想到是一个典型的观察者模式的事件。看看相关$on,$emit代码。我加点注解:
vue.prototype.$on = function (event, fn) {  var this$1 = this;  var vm = this;  if (array.isarray(event)) {   for (var i = 0, l = event.length; i < l; i++) { this$1.$on(event[i], fn); } } else { (vm._events[event] || (vm._events[event] = [])).push(fn);//存入事件 // optimize hook:event cost by using a boolean flag marked at registration // instead of a hash lookup if (hookre.test(event)) { vm._hashookevent = true; } } return vm }; vue.prototype.$emit = function (event) { var vm = this; console.log(vm); { var lowercaseevent = event.tolowercase(); if (lowercaseevent !== event && vm._events[lowercaseevent]) { tip( "event \"" + lowercaseevent + "\" is emitted in component " + (formatcomponentname(vm)) + " but the handler is registered for \"" + event + "\". " + "note that html attributes are case-insensitive and you cannot use " + "v-on to listen to camelcase events when using in-dom templates. " + "you should probably use \"" + (hyphenate(event)) + "\" instead of \"" + event + "\"." ); } } var cbs = vm._events[event]; console.log(cbs); if (cbs) { cbs = cbs.length > 1 ? toarray(cbs) : cbs;   var args = toarray(arguments, 1);   for (var i = 0, l = cbs.length; i < l; i++) {   cbs[i].apply(vm, args);//当emit的时候调用该事件。注意上面说的vue在初始化的守候。用bind修饰了事件函数。所以组件上挂载的事件都是在父作用域中的   }  }  return vm  };
看了上面的on,emit用法下面这个demo也就瞬间秒解了(一个经常用的非父子组件通信):
var bus = new vue() // 触发组件 a 中的事件 bus.$emit('id-selected', 1) // 在组件 b 创建的钩子中监听事件 bus.$on('id-selected', function (id) {  // ... })
是不是豁然开朗。
又到了愉快的总结时间了。segementfault的编辑器真难用。内容多就卡。哎。烦。卡的时间够看好多肥皂剧了。
总的来说。vue对于事件有两个底层的处理逻辑。
1:普通html元素和在组件上挂了.native修饰符的事件。最终eventtarget.addeventlistener() 挂载事件
2:组件上的,vue实例上的事件会调用原型上的$on,$emit(包括一些其他api $off,$once等等)
相信看了本文案例你已经掌握了方法,更多精彩请关注其它相关文章!
推荐阅读:
怎样使用vue.js与element-ui实现菜单树形结构
怎样使用vue内.sync修饰符
以上就是如何使用vue源码解析事件机制的详细内容。
该用户其它信息

VIP推荐

免费发布信息,免费发布B2B信息网站平台 - 三六零分类信息网 沪ICP备09012988号-2
企业名录 Product