在利用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. SVN代码丢失惊魂

    吓死了吓死了!要是那些代码丢了的话,要重新码一遍,我宁愿去吃屎. 某天快下班走人的时候,从SVN服务器update了本地代码,结果发现代码变回了上个月的样子.看SVN的日志,发现提交记录从6月22日一 ...

  2. how to use datatables editor

    Basic initialisation Editor is a Create, Read, Update and Delete (CRUD) extension forDataTables that ...

  3. 深入理解Java执行时数据区

    前情回想 在本专栏的前12篇博客中. 我们主要大致介绍了什么是JVM, 而且具体介绍了class文件的格式. 对于深入理解Java, 或者深入理解运行于JVM上的其它语言, 深入理解class文件格式 ...

  4. Latex 3: 解决LaTeX编译卡顿问题

    1.问题: 最近在编译latex时,老是在tulmr.fd处编译很久,但是以前不这样啊,那肯定就是我最近做了什么导致这样的了,是什么呢? 2.解决: 后来google下发现了解决办法,原来是我新安装了 ...

  5. caution

    做好需求更改的准备,提高代码的扩展性和可维护性:预留出修改bug和需求的时间:对需求理解透彻再开始写代码:代码不要写死,防止需求变动. 

  6. Oracle - 查询语句 - 分组函数

    /* 分组函数 不能再select子句中出现普通的列,除非这个列在group by中给出 所有的空值都会被分为一组 分组过滤 SELECT FROM WHERE GROUPBY HAVING ORDE ...

  7. What's the difference between HEAD, working tree and index, in Git?

    What's the difference between HEAD, working tree and index, in Git?

  8. bleve搜索引擎源码分析之索引——mapping真复杂啊

    接下来看看下面index部分的源码实现: data := struct { Name string Des string }{ Name: "hello world this is bone ...

  9. 在 Ubuntu 16.04 中安装支持 CPU 和 GPU 的 Google TensorFlow 神经网络软件

    TensorFlow 是用于机器学习任务的开源软件.它的创建者 Google 希望提供一个强大的工具以帮助开发者探索和建立基于机器学习的应用,所以他们在去年作为开源项目发布了它.TensorFlow ...

  10. zoj 3865

    Superbot Time Limit: 2 Seconds      Memory Limit: 65536 KB Superbot is an interesting game which you ...