在利用vue组件进行事件监听时发现,如果对N个vue组件实例的bus总线绑定同一事件的回调函数,触发任意组件的对应事件,回调函数至少会被执行N次,这是为什么呢?

为此,调研了普通对象的事件绑定和触发实现方式,参考:JavaScript实现自定义对象的自定义事件

其基本思想就是:设计一个原型对象,作为基类,其重点属性包括:一个_events对象数组属性,一个addEventListener方法,一个fireEvent方法,具体用途如下:

_eventys:对象数组属性,用于存储不同事件的处理函数,基本格式如下:

_eventys:{
eventName1:[ _callback1,_callback2,_callback3 ],
eventName2:[ _callback1,_callback2,_callback3... ],
...
}

addEventListener:用于向_events里面压入对应事件的处理函数,基本格式如下:

this.addEventListener:function( eventName,_callback){
this._events[eventName].push( _callback );
}

fireEvent: 用于触发对应事件的处理函数,基本格式如下:

this.fireEvent = function( eventName,e){
this._events[ eventName ].forEach( (function( f,i ){
f.call(this,e);
}).bind(this) );
}

基于上述原型对象构建一个工厂函数:

function CursomObject(){
  this._events = {},
}
CursomObject.prototype = {
addEventlistener:fuction(){},
fireEvent:fucntion(){}
};

接着,另外构建一个工厂函数,其中,这个工厂函数的原型是上述原型对象的一个new出来的对象,

function Test(){
}
Test.prototype = new CursomObject();

这样,基于Test构造函数可以构造很多对象出来,可以给每一个对象尝试绑定一下事件,再试着触发一下,基本代码如下:

var o1 = new Test();
var o2 = new Test();
o1.addEventListener( 'change',function(){
console.log( 'change1' );
} );
o2.addEventListener( 'change',function(){
console.log( 'change2' );
} );
o1.fireEvent( 'change' );

在控制台查看打印结果:

明明只触发了o1的事件,为什么o1,o2的'change'事件的回调函数都执行了!需要去看一下o1里的'change'数组属性都存储了什么,截图如下:

很显然,o1的_events['change']里存储了两个_callback回调函数,为什么呢?我们打印o1对象细节看一下:

原来o1的_events[ 'change' ]是继承自原型对象的,也就是这个new CursomObject()

o2也继承自该对象,因为o1,o2没有自己的_events属性,因此,在事件处理时,this指针不断上溯原型链,直到找到原型对象里的_events对象,因此,o1,o2针对_events的操作实际都是对该原型对象的操作,这样_events[ 'change' ]里存储了多个_callaback回调函数也就解释的通了。我们可以打印一下o2看下:

结果是一致的,那么解决的思路就是让o1,o2拥有自己独立的_events属性,这样每个_callback就存储在对象自己的_events里面了,怎么处理呢,我们看到CursomObject函数具有给调用对象创建_events属性的功能,因此,我们在用test构建对象的时候,对对象调用该方法就可以了,因此改写Test构造函数如下:

function Test(){
CursomObject.call( this );
}
Test.prototype = new CursomObject();

此后,我们再在浏览器里刷新并打印o1,o2信息如下:

此时,o1,o2已经分别具备了自己作用域内的_events属性,因此_callback回调的执行不用再上溯至原型对象,问题也就解决了。

Vue内bus总线对象在触发事件时,也有类似的问题出现,我们在扩展bus总线时,代码如下:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
        <script type="text/javascript" src="../../js/vue.js" ></script>
    </head>
    <body>
        <div id="app">
            <!--不native之前,绑定的都是自定义事件,native之后,绑定的是原生的click事件-->
            <child content='Dell' ref="child1"></child>
            <child content='Lee' ref="child2"></child>
            <child content='Hi'></child>
            <div @click="handleClick">parent</div>
        </div>
    </body>
    <script>
        //Bus总线模式/发布订阅模式/观察者模式
        Vue.prototype.bus = new Vue();
        var child={
            props:['content'],
            template:'<div @click="handleClick">{{content}}</div>',
            methods:{
                handleClick:function(){
                    this.bus.$emit('change',this.content);
                }
            },
            mounted:function(){
                console.log('mounted');
                this.bus.$on('change',function( msg ){
                    console.log( msg );
                });
            }
        };
        var vm = new Vue({
            el:'#app',
            components:{
                child:child
            },
            methods:{
                handleClick:function(  ){
                    console.log( this );
                },
                handleChange:function(){
                    alert('change');
                }
            }
        });
    </script>
</html>

从第一行可知,每个Vue实例的bus属性都是来自原型内的new Vue()对象,因此,所以的Vue实例共享该bus总线对象,每个组件(本质上也是Vue实例)的$on绑定的_calback回调函数都绑定到了该bus对象的类_events属性里,因此对单个组件$emit触发事件时,会发现执行了至少N次(N=组件数),例如,我们点击'Dell',打印信息如下:

基于上一个例的解决方案,其实解决起来也很简单,让每个组件有自己的bus属性就行了,这样每次绑定都是push进自己的bus对象的类_events属性里,具体代码如下:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<script type="text/javascript" src="../../js/vue.js" ></script>
</head>
<body>
<div id="app">
<!--不native之前,绑定的都是自定义事件,native之后,绑定的是原生的click事件-->
<child content='Dell' ref="child1"></child>
<child content='Lee' ref="child2"></child>
<child content='Hi'></child>
<div @click="handleClick">parent</div>
</div>
</body>
<script>
//Bus总线模式/发布订阅模式/观察者模式
//Vue.prototype.bus = new Vue();
var child={
props:['content'],
data:function(){
return {
bus : new
Vue()
};
},

template:'<div @click="handleClick">{{content}}</div>',
methods:{
handleClick:function(){
this.bus.$emit('change',this.content);
}
},
mounted:function(){
console.log('mounted');
this.bus.$on('change',function( msg ){
console.log( msg );
});
}
};
var vm = new Vue({
el:'#app',
components:{
child:child
},
methods:{
handleClick:function( ){
console.log( this );
},
handleChange:function(){
alert('change');
}
}
});
</script>
</html>

之后在浏览器内刷新查看执行结果:

Dell的change事件仅被执行一次,问题得以解决。

-------------------问题补充-----------------------------------------------------------------------------------------------

在深入了解bus总线的设计需求后,知道,bus总线设计之所以采取给Vue原型扩展bus属性,就是为了让所有子组件之间共享该bus属性,进而进行消息通信,因此上述的多次响应是一种合理的处理,让共享的bus属性对象监听bus自身的事件,然后bus响应每一个组件push到自己内部的_callback函数,总体设计思想如下:

在这种总线设计模式下,当在每一个组件上触发事件时,其实bus绑定的来自每个组件的_callback事件回调函数都会被执行,所以可以在_callback里进行消息的传递和接受,进而实现组件间的互相通信。

vue组件中—bus总线事件回调函数多次执行的问题的更多相关文章

  1. Vue 组件中 data 为什么必须是函数

    原文地址 vue组件中的data必须是函数 类比引用数据类型 Object是引用数据类型,如果不用function 返回,每个组件的data 都是内存的同一个地址,一个数据改变了其他也改变了; jav ...

  2. vue组件中的data为什么是函数?

    一.vue组件中的data为什么是函数 为了保证组件的独立性 和 可 复用性,data 是一个函数,组件实例化的时候这个函数将会被调用,返回一个对象,计算机会给这个对象分配一个内存地址,你实例化几次, ...

  3. Vue组件中的Data为什么是函数。

    简单点说,组件是要复用的,在很多地方都会调用.   如果data不是函数,而是属性,就又可能会发生多个地方的相同组件操作同一个Data属性,导致数据混乱. 而如果是函数,因为组件data函数的返回值是 ...

  4. Qt 学习之路 2(19):事件的接受与忽略(当重写事件回调函数时,时刻注意是否需要通过调用父类的同名函数来确保原有实现仍能进行!有好几个例子。为什么要这么做?而不是自己去手动调用这两个函数呢?因为我们无法确认父类中的这个处理函数有没有额外的操作)

    版本: 2012-09-29 2013-04-23 更新有关accept()和ignore()函数的相关内容. 2013-12-02 增加有关accept()和ignore()函数的示例. 上一章我们 ...

  5. tp5模型事件回调函数中不能使用$this

    tp5模型事件回调函数中不能使用$this,使用会报错,涉及到数据库操作使用Db类,不能使用$this->save()之类的方式 如果回调函数中需要使用类内函数,需要将函数定义为static,通 ...

  6. 深入理解--VUE组件中数据的存放以及为什么组件中的data必需是函数

    1.组件中数据的存放 ***(重点)组件是一个单独模块的封装:这个模块有自己的HTML模板,也有data属性. 只是这个data属性必需是一个函数,而这个函数返回一个对象,这个对象里面存放着组件的数据 ...

  7. 在vue项目中 如何定义全局变量 全局函数

    如题,在项目中,经常有些函数和变量是需要复用,比如说网站服务器地址,从后台拿到的:用户的登录token,用户的地址信息等,这时候就需要设置一波全局变量和全局函数 定义全局变量 原理: 设置一个专用的的 ...

  8. 15.Vue组件中的data

    1.组件中展示数据和响应事件: // 1. 组件可以有自己的 data 数据 // 2. 组件的 data 和 实例的 data 有点不一样,实例中的 data 可以为一个对象 // 3. 但是组件中 ...

  9. Vue组件中引入jQuery

    一.安装jQuery依赖 在使用jQuery之前,我们首先要通过以下命令来安装jQuery依赖: npm install jquery --save # 如果你更换了淘宝镜像,可以使用cnpm来安装, ...

随机推荐

  1. Linux(Ubuntu14.04)下Google Chrome / Chromium标题栏乱码问题

    注:我使用的Linux发行版是Ubuntu 14.04,不同Linux发行版可能会有不同. 最近在使用Chromium的时候tab的标题栏中文显示乱码,在地址栏输入中文是同样时乱码,就像下图: 看起来 ...

  2. sql 统计 url字符串处理

    SELECT SUBSTRING_INDEX(url,'/',1) AS wed_domain,COUNT(1),SUM(no_open_times),SUM(no_ad_times),SUM(ok_ ...

  3. A - BBQ Easy

    Score : 200 points Problem Statement Snuke is having a barbeque party. At the party, he will make N  ...

  4. Linux __setup解析【转】

    本文转载自:http://blog.csdn.net/fdaopeng/article/details/7895037 __setup这条宏在Linux Kernel中使用最多的地方就是定义处理Ker ...

  5. android 设置textview中划线效果

    textView.getPaint().setFlags(Paint. UNDERLINE_TEXT_FLAG ); //下划线   textView.getPaint().setAntiAlias( ...

  6. apktool工具下载地址

    apktool工具下载地址 http://ibotpeaches.github.io/Apktool/

  7. wpa_supplicant drivers 查看跟踪

    /**************************************************************************** * wpa_supplicant drive ...

  8. 并不对劲的bzoj5342:loj2554:uoj401:p4566: [Ctsc2018]青蕈领主

    题目大意 \(T\)(\(T\leq100\))组询问 有\(1\)到\(n\)(\(n\leq50000\))这\(n\)个整数组成的一个排列 定义这个排列的一个子区间是"连续" ...

  9. 洛谷 P1313 计算系数 —— 水题

    题目:https://www.luogu.org/problemnew/show/P1313 不就是...C(k,n) * an * bm . 代码如下: #include<iostream&g ...

  10. hdu 2476 (string painter) ( 字符串刷子 区间DP)

    String painter Time Limit: 5000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)To ...