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

这次给大家带来如何使用vue源码解析事件机制,使用vue源码解析事件机制的注意事项有哪些,下面就是实战案例,一起来看一下。

click1

click2

var Child = { template: '

a custom component!

'} Vue.component('my-component', { name: 'my-component', template: '

a custom component!

test click

', 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) } }})

登录后复制

上面的demo中一共有四个事件。基本涵盖了vue中最经典的事件的四种情况

普通html元素上的事件

好吧。想想我们还是一个个来看。如果懂vue组件相关的机制会更容易懂。那么首先我们看看最简单的第一、二个(两个事件只差了个修饰符):

click1

登录后复制

这是简单到不能在简单的一个点击事件。

我们来看看建立这么一个简单的点击事件,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); }}//接着看看initMethodsfunction 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  );  } } }}//我们接着看看bindfunction 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元素应该是不同的道路。事实也是如此:

 

登录后复制

最简单的一个例子。两个事件的区别就是一个有.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 modifierdata.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  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源码解析事件机制的详细内容,更多请关注【创想鸟】其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至253000106@qq.com举报,一经查实,本站将立刻删除。

发布者:PHP中文网,转转请注明出处:https://www.chuangxiangniao.com/p/2750659.html

(0)
上一篇 2025年3月8日 05:40:07
下一篇 2025年3月8日 05:40:16

AD推荐 黄金广告位招租... 更多推荐

相关推荐

发表回复

登录后才能评论