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

2019-7-23更新:

1. document和body代理事件,在某些浏览器无效,目前发现在微信浏览器无效。

2. 前端使用模板引擎渲染的节点,可能滞后与事件的绑定,因此这种情况下最适合事件代理。

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

 

事件冒泡是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]事件委托中冒泡情况的探讨_第1张图片

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

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

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

 

 

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

 

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

 

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

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

 

 

 

 

因为事件委托,冒泡从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匹配的事件都触发。

 

 

 

 

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

 

 

 

 

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

 

 

 

 

        因为事件委托,冒泡从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匹配的事件。

 

 

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

 

 

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

 

 

 

 

因为事件委托,冒泡从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'直接绑定的事件触发。


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

 

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

 

 

 

 

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

 

 

因为事件委托,冒泡从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结束点击事件。


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

 

 

 

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

 

 

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

 

 

 

 

因为事件委托,冒泡从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!');
            });
    $('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匹配的事件触发。


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

 

 

 

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

 

 

 

 

使用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!');
            });
    $('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

 

 

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

 

 

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

 

 

 

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

 

 

 

六、含选择器‘a’

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

点击8和9输出相同内容

 

七、含选择器‘img’

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

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


 

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

 

 

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

 

 

 

 

 

 

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

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;
	}
    });
	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]事件委托中冒泡情况的探讨_第3张图片

 

 

 

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

 

 

你可能感兴趣的:(前端文章,JavaScript文章,jQuery文章)