首页 > web前端 > js教程 > 正文

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

php中世界最好的语言
发布: 2018-06-02 14:28:45
原创
1567人浏览过

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

<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组件相关的机制会更容易懂。那么首先我们看看最简单的第一、二个(两个事件只差了个修饰符):

1

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

登录后复制

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

我们来看看建立这么一个简单的点击事件,vue中发生了什么。

1:new Vue()中调用了initState(vue):看代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

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:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

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的时候。会调用核心函数:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

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元素应该是不同的道路。事实也是如此:

1

2

<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函数在执行时。如果遇到组件。看过上一章的可以知道。会执行

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

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

}

登录后复制

整段代码关于事件核心操作:

1

2

3

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的时候。它会进入一下分支:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

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代码。我加点注解:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

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也就瞬间秒解了(一个经常用的非父子组件通信):

1

2

3

4

5

6

7

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等等)

相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!

推荐阅读:

怎样使用vue.js与element-ui实现菜单树形结构

怎样使用vue内.sync修饰符

以上就是如何使用vue源码解析事件机制的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习
PHP中文网抖音号
发现有趣的

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号