jquery的事件命名空间详解

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了jquery的事件命名空间详解相关的知识,希望对你有一定的参考价值。

jquery现在的事件API:on,off,trigger支持带命名空间的事件,当事件有了命名空间,就可以有效地管理同一事件的不同监听器,在定义组件的时候,能够避免同一元素应用到不同组件时,同一事件类型之间的影响,还能控制一些意外的事件冒泡。在实际工作中,相信大家都用的很多,但是不一定了解它的所有细节,至少我有这样的经验,经常在碰到疑惑的时候,还得重新写例子去验证它的相关作用,所以本文想把事件命名空间相关的细节都梳理出来,将来再犯迷糊的时候可以回来翻着看看以便加深对它的理解和运用。

在详细了解命名空间之前,得先认识下什么是自定义事件,因为命名空间可以同时应用于自定义事件和浏览器默认事件当中。

1. 自定义事件

我们在定义组件的时候,浏览器的默认事件往往不能满足我们的要求,比如我们写了一个树形组件,它有一个实例方法init用来完成这个组件的初始化工作,在这个方法调用结束之后,我们通常会自定义一个init事件,以便外部可以在树组件初始化完成之后做一些回调处理:

<script src="../js/lib/jquery.js"></script>
<div id="tree">

</div>
<script>
    var Tree = function(element, options) {
        var $tree = this.$tree = $(element);
        //监听init事件,触发
        $tree.on(\'init\', $.proxy(options.onInit, this));
        this.init();
    };

    Tree.prototype.init = function() {
        console.log(\'tree init!\');
        this.$tree.trigger(\'init\');
    };

    var tree = new Tree(\'#tree\', {
        onInit: function() {
            console.log(this.$tree.outerHeight());
        }
    });
</script>

以上代码中.on(\'init\',…)中的init就是一个类似click这样的自定义事件,该代码运行结果如下

image
自定义事件的使用就跟浏览器默认事件的使用没有任何区别,就连事件冒泡和阻止事件默认行为都完全支持,唯一的区别在于:浏览器自带的事件类型可以通过浏览器的UI线程去触发,而自定义事件必须通过代码来手动触发:
image

2. 事件命名空间

事件命名空间类似css的类,我们在事件类型的后面通过点加名称的方式来给事件添加命名空间:

<script>
    var Tree = function(element, options) {
        var $tree = this.$tree = $(element);
        //监听init事件,触发
        $tree.on(\'init.my.tree\', $.proxy(options.onInit, this));
        this.init();
    };

    Tree.prototype.init = function() {
        console.log(\'tree init!\');
        this.$tree.trigger(\'init.my.tree\');
    };

    var tree = new Tree(\'#tree\', {
        onInit: function() {
            console.log(this.$tree.outerHeight());
        }
    });
</script>

以上代码中.on(\'init.my.tree\',…)通过.my和.tree给init这个事件添加了2个命名空间,注意命名空间是类似css的类,而不是类似java中的package,所以这两个命名空间的名称分别是.my和.tree,而不是my和my.tree,注意命名空间的名称前面一定要带点,这个名称在off的时候可以用到。在监听和触发事件的时候带上命名空间,当触发带命名空间的事件时,只会调用匹配该命名空间的监听器。所以命名空间可以有效地管理同一事件的不同监听器,尤其在定义组件的时候可以有效地保证组件内部的事件只在组件内部有效,不会影响到其它组件。

现在假设我们不用命名空间,同时定义两个组件Tree和Dragable,并且同时对#tree这个元素做实例化,以便实现一棵可以拖动的树:

<script>
    var Tree = function(element, options) {
        var $tree = this.$tree = $(element);
        //监听init事件,触发
        $tree.on(\'init\', $.proxy(options.onInit, this));
        this.init();
    };

    Tree.prototype.init = function() {
        console.log(\'tree init!\');
        this.$tree.trigger(\'init\');
    };

    var tree = new Tree(\'#tree\', {
        onInit: function() {
            console.log(this.$tree.outerHeight());
        }
    });

    var Dragable = function(element, options) {
        var $element = this.$element = $(element);
        //监听init事件,触发
        $element.on(\'init\', $.proxy(options.onInit, this));
        this.init();
    };

    Dragable.prototype.init = function() {
        console.log(\'tree init!\');
        this.$element.trigger(\'init\');
    };

    var drag = new Dragable(\'#tree\', {
        onInit: function() {
            console.log(\'start drag!\');
        }
    });
</script>

结果会发现Tree的onInit回调被调用两次:
image
根本原因就是因为#tree这个元素被应用到了多个组件,在这两个组件内部对#tree这个元素定义了同一个名称的事件,所以后面实例化的组件在触发该事件的时候也会导致前面实例化的组件的同一事件再次被触发。通过命名空间就可以避免这个问题,让组件各自的事件回调互不影响:

<script>
    var Tree = function(element, options) {
        var $tree = this.$tree = $(element);
        //监听init事件,触发
        $tree.on(\'init.my.tree\', $.proxy(options.onInit, this));
        this.init();
    };

    Tree.prototype.init = function() {
        console.log(\'tree init!\');
        this.$tree.trigger(\'init.my.tree\');
    };

    var tree = new Tree(\'#tree\', {
        onInit: function() {
            console.log(this.$tree.outerHeight());
        }
    });

    var Dragable = function(element, options) {
        var $element = this.$element = $(element);
        //监听init事件,触发
        $element.on(\'init.my.dragable\', $.proxy(options.onInit, this));
        this.init();
    };

    Dragable.prototype.init = function() {
        console.log(\'drag init!\');
        this.$element.trigger(\'init.my.dragable\');
    };

    var drag = new Dragable(\'#tree\', {
        onInit: function() {
            console.log(\'start drag!\');
        }
    });
</script>

这样tree实例的onInit就不会被调用2次了:

image

3. 命名空间的匹配规则

在第2部分的举例当中,触发带命名空间的事件时,触发方式是:
image
然后就会调用这里监听的回调:
image
如果把触发方式改一下,不改监听方式,改成以下三种方式的一种,结果会怎么样呢:

this.$element.trigger(\'init\');
this.$element.trigger(\'init.dragable\');
this.$element.trigger(\'init.my\');

答案是该监听回调依然会被调用。这个跟命名空间的匹配规则有关,为了说明这个规则,可以用以下的这个代码来测试:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>xxxxx</title>
    <style type="text/css">
        #parent {
            margin: 100px auto 0 auto;
            width: 600px;
            height: 200px;
            border: 1px solid #ccc;
            position: relative;
        }

        .log {
            position: absolute;
            left: 0;
            width: 100%;
            height: 100%;
            overflow: auto;
        }

        p {
            margin: 0;
        }

        #btns {
            margin: 20px auto 0 auto;
            width: 600px;
        }
    </style>
</head>
<body>
<script src="../js/lib/jquery.js"></script>
<div id="parent">
    <div class="log"></div>
</div>
<div id="btns">
    <button id="btn1" type="button" onclick="$p.trigger(\'click.n1.n2.n3.n4\');">trigger(\'click.n1.n2.n3.n4\')</button>
    <button id="btn2" type="button" onclick="$p.trigger(\'click.n1.n2.n3\');">trigger(\'click.n1.n2.n3\')</button>
    <button id="btn3" type="button" onclick="$p.trigger(\'click.n1.n2\');">trigger(\'click.n1.n2\')</button>
    <button id="btn4" type="button" onclick="$p.trigger(\'click.n1\');">trigger(\'click.n1\')</button>
    <button id="btn5" type="button" onclick="$p.trigger(\'click\');">trigger(\'click\')</button>
</div>
<script>
    function log($e, msg) {
        var $log = $e.find(\'.log\');
        $log.append(\'<p>\' + msg + \'</p>\');
    }

    var $p = $(\'#parent\');

    $p.on(\'click.n1.n2.n3.n4\', function(){
        log($p, \'click n1 n2 n3 n4\');
    });
    $p.on(\'click.n1.n2.n3\', function(){
        log($p, \'click n1 n2 n3\');
    });
    $p.on(\'click.n1.n2\', function(){
        log($p, \'click n1 n2\');
    });
    $p.on(\'click.n1\', function(){
        log($p, \'click n1\');
    });
    $p.on(\'click\', function(){
        log($p, \'click\');
    });

</script>
</body>
</html>

初始化效果如下:
image 
依次点击界面上的按钮(不过点击按钮前得先刷新页面,这样的话各个按钮效果才不会混在一起),界面打印的效果如下:
image
image
image
image
image

以上的测试代码一共给$p元素的click事件定义了4个命名空间,然后针对不同的命名空间数量,添加了五个监听器,通过外部的按钮来手动触发各个带命名空间的事件,从最后的结果,我们能得出这样一个规律:
1)当触发不带命名空间的事件时,该事件所有的监听器都会触发;(从最后一个按钮的测试结果可看出)
2)当触发带一个命名空间的事件时,在监听时包含该命名空间的所有监听器都会被触发;(从第4个按钮的测试结果可看出)
3)当触发带多个命名空间的事件时,只有在监听时同时包含那多个命名空间的监听器才会被触发;(从第2,3个按钮的测试结果可看出)
4)只要触发带命名空间的事件,该事件不带命名空间的监听器就不会被触发;(从1,2,3,4个按钮可看出)
5)2跟3其实就是一个,2是3的一种情况

这个规律完全适用于浏览器默认事件和自定义事件,自定义事件的测试可以用下面的代码,结论是一致的:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>xxxxx</title>
    <style type="text/css">
        #parent {
            margin: 100px auto 0 auto;
            width

以上是关于jquery的事件命名空间详解的主要内容,如果未能解决你的问题,请参考以下文章

jquery的事件命名空间详解

jQuery 事件用法详解

jQuery 事件用法详解

jQuery 事件用法详解

如何将自定义事件添加到 jQuery 插件模式中

彻底弄懂jQuery事件原理一