欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

阻止事件冒泡和围绕.on()方法 [.selector]事件委托中冒泡情况的探讨

程序员文章站 2022-05-29 19:46:33
...

、事件冒泡和阻止事件冒泡

事件冒泡是JavaScript的核心概念之一,它的原理很简单,但真正应用起来还是有不少的坑。

这里说一说关于阻止事件冒泡的本质。

我们都知道阻止事件冒泡的方法:

1. event.stopPropagation(),停止冒泡。

2.在事件触发的执行函数Handler中写return false,对事件对象停止冒泡和停止默认行为。


需要注意:

event.stopPropagation()停止事件冒泡,阻止从绑定的元素向上冒泡。

$('#main').on('click', 'li', function () {
    // do somthing
    event.stopPropagation();
}) ;

click事件冒泡到#main就不再向上冒泡,并不是li往上不冒泡。


return false,实际上是终结了这个(点击)事件,冒泡当然也就停止了。在后述的测试中会分析。


二、 .on()方法的 [.selector]

 

.on( events [,selector ] [, data ], handler(eventObject) )

jQuery API中对.on()方法的定义:
.on()方法事件处理程序到当前选定的jQuery对象中的元素。在jQuery 1.7中,.on()方法 提供绑定事件处理的所有功能。为了帮助从旧的jQuery事件方法转换过来,查看 .bind(), .delegate(), 和 .live()。


2.1 不加入[.selector],相当于以前的.bind(),简单粗暴直接绑定事件

文档:如果省略selector或者是null,那么事件处理程序被称为直接事件 或者 直接绑定事件 。每次选中的元素触发事件时,就会执行处理程序,不管它直接绑定在元素上,还是从后代(内部)元素冒泡到该元素的。

这要分两种情况:

(1)当要在目标元素上直接触发事件,而不需要事件委托时,那么直接用目标元素调用.on(),例如:

$(‘div’).on(‘click’,func);

同时用event.stopPropagation()或return false阻止事件从目标元素(div)向上冒泡;

(2)需要进行事件委托。

        例如点击ul列表中的li触发事件,应当采用事件委托。那么绑定的元素可以是li的父元素ul,或者是body、document。

以绑定ul为例:

$('ul').on('click', function () {
// do something;
});

        当不加入[.selector],那么点击ul以及ul之下的任意元素都会触发事件。

        但通常情况下,我们进行事件委托都是要委托给绑定元素之一的一个特定元素,而不是委托给任意一个元素。所以可以不考虑采用这种方式,而是下面的。


2.2  加入[.selector],真正意义上的事件委托

文档:当提供selector参数时,事件处理程序是指为委派 事件(愚人码头注:通常也有很多人叫它代理事件)。事件不会在直接绑定的元素上触发,但当selector参数选择器匹配到后代(内部元素)的时候,事件处理函数才会被触发。jQuery会从 event.target 开始向上层元素(例如,由最内层元素到最外层元素)开始冒泡,并且在传播路径上所有绑定了相同事件的元素若满足匹配的选择器,那么这些元素上的事件也会被触发。

——加入[.selector]的作用实际上是规范了事件委托。


        例如点击li触发事件,li的父元素ul,或者是body、document,就可写为:

$('ul').on('click', 'li', function () {
    // do something;
});

注意文档中标红的文字,可以这样理解:

当selector参数选择器匹配的内部元素(这里是li)及其后代的元素被点击,意味着传播路径经过了这个匹配的元素,事件得以触发。(注意不是只有li被点击会触发,因为冒泡传播,其后代的元素被点击也会触发事件。)


我们通过一个测试,来验证上述理论,并从中发现一些规则。我们测试的是点击最外层盒子'#propa'本身及其后代的元素,触发事件的情况,着重考虑点击li的情况。

阻止事件冒泡和围绕.on()方法 [.selector]事件委托中冒泡情况的探讨

一、不含选择器直接绑定,带有事件委托功能

            $('#propa').on('click', function () {
                console.log(event.target);
            });

点击1,2,3,4,5,7,8,9都会输出

阻止事件冒泡和围绕.on()方法 [.selector]事件委托中冒泡情况的探讨

因为冒泡,点击最外层盒子'#propa'之内的任意元素都会触发事件


二、采取事件委托。含选择器‘li’。

            $('#propa').on('click', 'li', function () {
                console.log(event.target);
            });

点击1,2,3会输出,li和li的后代元素触发

阻止事件冒泡和围绕.on()方法 [.selector]事件委托中冒泡情况的探讨

因为事件委托,冒泡从event.target直至#propa,只有传播过程中遇到li才会触发,包括点击lili的后代元素。


三-1、用'#propa'事件委托. 为’li’和’li’下面的第一代div匹配事件,针对$('#propa').on使用event.stopPropagation()

            $('#propa').on('click', '.li-div', function () {
                console.log(event.target);
                event.stopPropagation();
            });
            $('#propa').on('click', 'li', function () {
                console.log('prop triggered!');
            });

点击1会输出如下,冒泡从event.target至#propa 。为li-div和li匹配的事件都触发。

阻止事件冒泡和围绕.on()方法 [.selector]事件委托中冒泡情况的探讨

点击2会输出如下,冒泡从event.target至#propa 。为li-div和li匹配的事件都触发。

阻止事件冒泡和围绕.on()方法 [.selector]事件委托中冒泡情况的探讨

点击3会输出如下,冒泡从event.target至#propa 。只匹配到li。

阻止事件冒泡和围绕.on()方法 [.selector]事件委托中冒泡情况的探讨

        因为事件委托,冒泡从event.target直至#propa。先匹配到更内层的选择器(li-div),执行第1个函数,再匹配到外层的选择器(li),执行第2个函数。使用event.stopPropagation()阻止的是从最外层盒子‘#propa’向上冒泡。


三-2、用'#propa'事件委托. 为’li’和’li’下面的第一代div匹配事件,针对$('#propa').on使用return false

            $('#propa').on('click', '.li-div', function () {
                console.log(event.target);
                return false;
            });
            $('#propa').on('click', 'li', function () {
                console.log('proptriggered!');

点击1会输出如下,冒泡从event.target至#propa ,为li-div匹配的事件触发,然后return false立即结束点击事件,不触发为 li匹配的事件。

阻止事件冒泡和围绕.on()方法 [.selector]事件委托中冒泡情况的探讨

点击2会输出如下,冒泡从event.target至#propa ,为li-div匹配的事件触发,然后return false立即结束点击事件,不触发为 li匹配的事件。

阻止事件冒泡和围绕.on()方法 [.selector]事件委托中冒泡情况的探讨

点击3会输出如下,冒泡从event.target至#propa 。只匹配到li。

阻止事件冒泡和围绕.on()方法 [.selector]事件委托中冒泡情况的探讨

因为事件委托,冒泡从event.target直至#propa。先匹配到更内层的选择器(li-div),执行第1个函数,执行到return false语句后,立即结束点击事件,不会继续向上匹配,所以不执行第2个函数。


四-1、分别用'#propa'直接绑定和事件委托。事件委托为’li’匹配事件。针对 $('#propa')的直接绑定使用 event.stopPropagation()

            $('#propa').on('click', function () {
                console.log(event.target);
                event.stopPropagation();
            });
            $('#propa').on('click', 'li', function () {
                console.log('proptriggered!');
            });

点击1会输出如下,冒泡从event.target至#propa 。为li匹配的事件先触发,然后是'#propa'直接绑定的事件触发。

阻止事件冒泡和围绕.on()方法 [.selector]事件委托中冒泡情况的探讨
点击2会输出如下,冒泡从event.target至#propa 。为li匹配的事件先触发,然后是'#propa'直接绑定的事件触发
阻止事件冒泡和围绕.on()方法 [.selector]事件委托中冒泡情况的探讨

点击3会输出如下,冒泡从event.target至#propa 。为li匹配的事件先触发,然后是'#propa'直接绑定的事件触发

阻止事件冒泡和围绕.on()方法 [.selector]事件委托中冒泡情况的探讨

点击4、5会输出如下,冒泡从event.target至#propa,'#propa'直接绑定的事件触发

阻止事件冒泡和围绕.on()方法 [.selector]事件委托中冒泡情况的探讨

因为事件委托,冒泡从event.target直至#propa。为li匹配的事件触发,同时为#propa绑定两个事件,含有事件委托的先执行,直接绑定的后执行。

四-2、分别用'#propa'直接绑定和事件委托。事件委托为’li’匹配事件。针对 $('#propa')的直接绑定使用return false

            $('#propa').on('click', function () {
                console.log(event.target);
                return false;
            });
            $('#propa').on('click', 'li', function () {
                console.log('proptriggered!');

点击1会输出如下,冒泡从event.target至#propa 。为li匹配的事件先触发,然后是'#propa'直接绑定的事件触发,最后return false结束点击事件。

阻止事件冒泡和围绕.on()方法 [.selector]事件委托中冒泡情况的探讨
点击2会输出如下,冒泡从event.target至#propa 。为li匹配的事件先触发,然后是'#propa'直接绑定的事件触发,最后return false结束点击事件

阻止事件冒泡和围绕.on()方法 [.selector]事件委托中冒泡情况的探讨

点击3会输出如下,冒泡从event.target至#propa 。为li匹配的事件先触发,然后是'#propa'直接绑定的事件触发,最后return false结束点击事件

阻止事件冒泡和围绕.on()方法 [.selector]事件委托中冒泡情况的探讨

点击4、5会输出如下,冒泡从event.target至#propa,'#propa'直接绑定的事件触发。

阻止事件冒泡和围绕.on()方法 [.selector]事件委托中冒泡情况的探讨

因为事件委托,冒泡从event.target直至#propa。为li匹配的事件触发,同时为#propa绑定两个事件,含有事件委托的先执行,直接绑定的后执行,return false最后才发生,不会影响两个函数的执行。


五-1、用body事件委托. 为’li’和’li’下面的第一代div匹配事件,针对$('body').on使用event.stopPropagation()。用#propa事件委托,为’li’匹配事件。

	    $('body ').on('click', '.li-div', function () {
                console.log(event.target);
                event.stopPropagation();
            });
            $('#propa').on('click', 'li', function () {
                console.log('prop triggered!');
            });

点击1会输出如下,先是冒泡从event.target至#propa ,为li匹配的事件先触发,然后冒泡至body,为li的第一代div匹配的事件触发。

阻止事件冒泡和围绕.on()方法 [.selector]事件委托中冒泡情况的探讨
点击2会输出如下,先是冒泡从event.target至#propa ,为li匹配的事件先触发,然后冒泡至body,为li的第一代div匹配的事件触发。

阻止事件冒泡和围绕.on()方法 [.selector]事件委托中冒泡情况的探讨

点击3会输出如下,冒泡从event.target至body,只匹配到li

阻止事件冒泡和围绕.on()方法 [.selector]事件委托中冒泡情况的探讨

使用event.stopPropagation()结束为body绑定的事件。

五-2、用body事件委托,为li’下面的第一代div匹配事件,针对$('body ').on使用return false。用#propa事件委托,为’li’匹配事件。

	    $('body ').on('click', '.li-div', function () {
                console.log(event.target);
                return false;
            });
            $('#propa').on('click', 'li', function () {
                console.log('proptriggered!');
            });

点击1会输出如下,先是冒泡从event.target至#propa ,为li匹配的事件先触发,然后冒泡至body,为li的第一代div匹配的事件触发,最后return false

阻止事件冒泡和围绕.on()方法 [.selector]事件委托中冒泡情况的探讨

点击2会输出如下,先是冒泡从event.target至#propa ,为li匹配的事件先触发,然后冒泡至body,为li的第一代div匹配的事件触发,最后return false

阻止事件冒泡和围绕.on()方法 [.selector]事件委托中冒泡情况的探讨

点击3会输出如下,冒泡从event.target至body,只匹配到li

阻止事件冒泡和围绕.on()方法 [.selector]事件委托中冒泡情况的探讨

为body绑定的事件函数中使用return false结束点击事件


六、含选择器‘a’

            $('#propa').on('click', 'a', function () {
                console.log('a is triggered!')
            });

点击8和9输出相同内容

阻止事件冒泡和围绕.on()方法 [.selector]事件委托中冒泡情况的探讨

七、含选择器‘img’

            $('#propa').on('click', 'img', function () {
                console.log('img is triggered!')
            })

点击9输出,点击8不输出

阻止事件冒泡和围绕.on()方法 [.selector]事件委托中冒泡情况的探讨


根据测试,得到绑定元素、选择器[,selector]、阻止冒泡方式(event.stopPropagation()或者return false)与事件处理队列和触发情况的关系。

阻止事件冒泡和围绕.on()方法 [.selector]事件委托中冒泡情况的探讨

最后,我们得出以下结论:

1.       事件冒泡是从内部event.target直至绑定的元素,event.stopPropagation()是阻止冒泡从绑定元素再往上传播;return false是执行完当前事件函数后立即终止该事件类型,冒泡、默认处理、具有相同事件类型的其它绑定事件都不再生效。

2.       事件执行的优先级

为不同元素绑定的事件,优先执行靠内层元素绑定的事件;

同一元素绑定的多个事件,优先执行事件委托(含有选择器),再执行直接绑定事件。

3.       根据上述1、2条,event.stopPropagation()不会影响内部event.target到绑定元素之间的冒泡传播过程。

同一事件类型的多个绑定,如果return false在先执行的事件处理函数终,会终结尚未执行的事件绑定(当然也终止了冒泡);如果在最后执行的事件处理函数中,不会影响冒泡传播过程。

4.       慎用外层元素(body,document等).on() +[,selector]的方式。当希望利用冒泡进行事件委托时,宜采用(body,document等).on() + [,selector]的方式,要注意冒泡会在一个很长的路径内传播,很可能触发其它元素的事件;不需要委托的一般情况下,则宜将内部元素直接绑定事件(不再加selector),使用event.stopPropagation(),就可以阻止冒泡从这个元素向上传。

5.       如果我们只是希望点击目标元素才触发事件,而不希望目标元素的后代元素向上冒泡导致事件被触发,那么方法为:主动增加一个停止冒泡的事件绑定,


方法1:绑定后代元素并在Handler函数中加入event.stopPropagation()

方法2:绑定后代元素并在Handler函数中加入return false, 或直接设置handler为false:

$(‘div *’).on(‘click’, false);

方法3:

    $('ul').on('click', 'li', function () {
	var ev = ev || window.event; 
      var target = ev.target || ev.srcElement;
      if(target.nodeName.toLowerCase() === 'li'){
            // do something;
	}
    });

例如搜索框,我们需要点击搜索框和智能选项下拉列表之外的区域时,列表消失,点击搜索框和列表本身不会消失,那么我们要给外层body绑定点击事件,但是要阻止内部的form点击冒泡到body。于是我们主动给form绑定一个事件,事件函数用function ( ) {return false;},简写为false;或function () { event.stopPropagation();}

	/*单纯阻止后代元素的冒泡*/
	$('form').on('click', function () {
		//return false或;
		event.stopPropagation();
	});
	/*后代元素冒泡被阻止了,可以放心绑定事件了*/
	$(document).on('click', function () {
		$('.smartUl').hide();
	});

阻止事件冒泡和围绕.on()方法 [.selector]事件委托中冒泡情况的探讨阻止事件冒泡和围绕.on()方法 [.selector]事件委托中冒泡情况的探讨
阻止事件冒泡和围绕.on()方法 [.selector]事件委托中冒泡情况的探讨

总的说来,搞清事件冒泡起始、终止节点、传播路径、停止冒泡方式、事件队列、触发方式,和它与.on()方法、[.selector]的关系,就基本能在实践中对事件的处理游刃有余。