zoukankan      html  css  js  c++  java
  • vue组件中—bus总线事件回调函数多次执行的问题

    在利用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里进行消息的传递和接受,进而实现组件间的互相通信。

    路漫漫其修远兮,吾将上下而求索。 May stars guide your way⭐⭐⭐
  • 相关阅读:
    js 闭包
    js 图片放大镜功能
    前端页面优化
    css 背景图片自适应分辨率大小 兼容
    纯css实现箭头
    js 排序算法
    css3 渐变 兼容
    css 兼容性总结
    css3 @keyframe 抖动/变色动画
    关于$.data(element,key,value)与ele.data.(key,value)的区别
  • 原文地址:https://www.cnblogs.com/surfer/p/9685339.html
Copyright © 2011-2022 走看看