当前位置:首页 > vue技术文章 > vue源码解析之事件机制原理

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

  • 发布时间:
  • 作者:码农之家原创
  • 点击:135

这篇文章主要知识点是关于vue、事件机制、的内容,如果大家想对相关知识点有系统深入的学习,可以参阅以下电子书

Vue企业开发实战
  • 类型:Vue.js实战大小:109.6 MB格式:PDF作者:肖睿
立即下载

vue源码解析之事件机制原理

上一章没什么经验。直接写了组件机制。感觉涉及到的东西非常的多,不是很方便讲。今天看了下vue的关于事件的机制。有一些些体会。写出来。大家一起纠正,分享。源码都是基于最新的Vue.js v2.3.0。下面我们来看看vue中的事件机制:
老样子还是先上一段贯穿全局的代码,常见的事件机制demo都会包含在这段代码中:

<div id="app">
 <div id="test1" @click="click1">click1</div>
 <div id="test2" @click.stop="click2">click2</div>
 <my-component v-on:click.native="nativeclick" v-on:componenton="parentOn">
 </my-component>
</div>
</body>
<script src="vue.js"></script>
<script type="text/javascript">
var Child = {
 template: '<div>A custom component!</div>'
} 
Vue.component('my-component', {
 name: 'my-component',
 template: '<div>A custom component!<div @click.stop="toParent">test click</div></div>',
 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组件相关的机制会更容易懂。那么首先我们看看最简单的第一、二个(两个事件只差了个修饰符):

<div id="test1" @click="click1">click1</div>

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

我们来看看建立这么一个简单的点击事件,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环节需要将该div变成ast(抽象语法树)。当编译到该div时经过核心函数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('div',{attrs:{"id":"test1"},on:{"click":click1}},[_v("click1")]),_v(" "),_c('div',{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等等)

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持码农之家。

以上就是本次给大家分享的关于java的全部知识点内容总结,大家还可以在下方相关文章里找到相关文章进一步学习,感谢大家的阅读和支持。

推荐内容

租车系统的Java简单实现

Python中使用aiomysql异步操作MySQL实例效果

如何使用jQuery.wechat构建微信WEB应用

关于jquery二维码生成插件qrcode详解

实例讲解java怎样利用CountDownLatch实现并行计算

展开 +

收起 -

vue 相关电子书
学习笔记
网友NO.866405

通过vue-cli3构建一个SSR应用程序的方法

1、前沿 1.1、什么是SSR SSR(服务端渲染)顾名思义就是将页面在服务端渲染完成后在客户端直接展示。 1.2、客户端渲染与服务端渲染的区别 传统的SPA模式 即客户端渲染的模式 Vue.js构建的应用程序,默认情况下是有一个html模板页,然后通过webpack打包生成一堆js、css等等资源文件。然后塞到index.html中 用户输入url访问页面 - 先得到一个html模板页 - 然后通过异步请求服务端数据 - 得到服务端的数据 - 渲染成局部页面 - 用户 SSR模式 即服务端渲染模式 用户输入url访问页面 - 服务端接收到请求 - 将对应请求的数据渲染完一个网页 - 返回给用户 1.3、为什么要使用SSR呢? ssr的好处官网已经给出,最有意思的两个优点如下: 更有好的SEO。由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面。 更快的内容到达时间(time-to-content) 1.4、SSR原理 这是vue.js官方的SSR原……

网友NO.329528

Vue匿名插槽与作用域插槽的合并和覆盖行为

Vue 测试版本: Vue.js v2.5.13 Vue 文档: slot 元素可以用一个特殊的特性 name 来进一步配置如何分发内容。多个插槽可以有不同的名字。具名插槽将匹配内容片段中有对应 slot 特性的元素。 仍然可以有一个匿名插槽,它是默认插槽,作为找不到匹配的内容片段的备用插槽。 具体应用的时候: 1、匿名插槽的合并行为: div id="app" myele div default slot /div div divfrom parent!/div divfrom parent!/div /div /myele /divwindow.onload = function() { Vue.component('myele', { template: ` div slot/slot /div ` }); new Vue({ el: '#app' });}; 效果:内容合并 2、匿名作用域插槽的覆盖行为: div id="app" myele div slot-scope="props" divfrom parent!/div divfrom {{props.text}}/div /div div slot-scope="prop" divfrom /div div{{prop.text}}/div /div /myele /divwindow.onload = function() { Vue.component('myele', { template: ` div slot text="child"/slot slot text="child2"/slot//即使 text 和上……

网友NO.689029

Vue监听事件实现计数点击依次增加的方法

1.实现计数器功能,每点击一次按钮,count值增加一或增加二,鼠标在cordinates行移动时会更新当前坐标,通过自定义函数或者stop属性禁止事件传播。 效果如下: 代码如下: !DOCTYPE htmlhtmlheadmeta charset="utf-8"title计数器自增函数/titlescript src="vue.js"/script/headbody div id="app" button v-on:click="increase"点击加一/button !--自定义步长-- button v-on:click="increase2(2,$event)"点击加二/button p{{count}}/p !--实现鼠标在此行移动时显示位置坐标-- p v-on:mousemove="updateCordinates" cordinates:({{x}}/{{y}})- !--下面两种方法实现的效果相同-- span v-on:mousemove="dummy"STOP UPDATE/span !--这里的stop后不能加小括号-- span v-on:mousemove.stopstop update too!/span /p /div script new Vue({ el:'#app', data:{ count:0, x:0, y:0 }, methods:{ increase:function(){ this.count++; }, increase2:function (step,event){ this.count+=step; }, updateCordinates:function(event){ this.x=event.clien……

网友NO.268126

vue.js实现含搜索的多种复选框(附源码)

前言 最近在重构一个复选框组件,原型是select2这个jQuery插件, 有兴趣的可以去搜下,封装的很好,但是并不能满足业务所有需求,最要命的是jquery插件这种以dom驱动数据的库,并不能和vue和angular这种数据驱动dom的框架很好的结合,所以我用vue的component重构了一下,走了不少弯路,做的demo分享出来,还请大家指点一二! download地址: vue_select2(jb51.net).rar 效果图如下,封装的应该是蛮抽象的了,只需要传入下拉框选项list,默认选中list,和回调callback三个参数可以了,在这里特别说明下父与子是如何通信的,因为这里走了不少弯路!! 父级往子组件传递数据,通过v-bind绑定数据,子组件接收props里的数据,通过watch监听数据改变。 子组件往父级传递数据,通过$dispatch派发,绑定到自定义的selected或inputed事件,再触发父级的回调。 总结 以上就是这篇文章……

网友NO.390061

Vue中使用方法、计算属性或观察者的方法实例详解

熟悉 Vue 的都知道 方法methods、计算属性computed、观察者watcher 在 Vue 中有着非常重要的作用,有些时候我们实现一个功能的时候可以使用它们中任何一个都是可以的,但是它们之间又存在一些不同之处,每一个都有一些适合自己的场景,我们要想知道合适的场景,肯定先对它们有一个清楚的了解,先看一个小例子。 div id="app" input v-model="firstName" type="text" input v-model="secondName" type="text" p{{fullName}}/p/div methods 方式 var vm = new Vue({ el: "#app", data: { firstName: "Mo", secondName: "deng", fullName: "" }, methods: { getFullName () { this.fullName = this.firstName + "" + this.secondName; } }, mounted () { this.getFullName(); }}); computed 方式 var vm = new Vue({ el: "#app", data: { firstName: "liu", secondName: "deng" }, computed: { fullName () { return this.firstName + "" + this.secondName; } }}); watcher 方式 var vm = new Vue({ el: "#app", data: { firstName: "l……

<
1
>

电子书 编程教程 文档 软件 源码 视频

Copyright 2018-2020 xz577.com 码农之家

本站所有电子书资源不再提供下载地址,只分享来路

免责声明:网站所有作品均由会员网上搜集共同更新,仅供读者预览及学习交流使用,下载后请24小时内删除

版权投诉 / 书籍推广 / 赞助:QQ:520161757