js事件委托机制





基本概念

事件代理(Event Delegation),又称事件委托。

事件委托是JavaScript中常用绑定事件的常用技巧,是把原本需要绑定在子元素的响应事件(click、keydown…)委托给父元素,让父元素担当事件监听的职务。

事件代理的原理是DOM元素的事件冒泡。

img

如上图所示,事件传播分成三个阶段:

  • 捕获阶段:从window对象传导到目标节点(上层传到底层)称为“捕获阶段”(capture phase),捕获阶段不会响应任何事件;
  • 目标阶段:在目标节点上触发,称为“目标阶段”
  • 冒泡阶段:从目标节点传导回window对象(从底层传回上层),称为“冒泡阶段”(bubbling phase)。事件代理即是利用事件冒泡的机制把里层所需要响应的事件绑定到外层;





事件冒泡的优点和缺点

优点

  • 那些需要创建的以及驻留在内存中的事件处理器少了,并降低了崩溃的风险。

  • 在DOM更新后无须重新绑定事件处理器了。如果页面是动态生成的,比如说通过Ajax,你不再需要在元素被载入或者卸载的时候来添加或者删除事件处理器了。

  • 可以大量节省内存占用,减少事件注册,比如在ul上代理所有li的click事件就非常棒

  • 可以实现当新增子对象时无需再次对其绑定(动态绑定事件)

<ul id="list">
  <li>item 1</li>
  <li>item 2</li>
  <li>item 3</li>
  ......
  <li>item n</li>
</ul>
// ...... 代表中间还有未知数个 li

如上面代码所示,如果给每个li列表项都绑定一个函数,那对内存的消耗是非常大的,因此较好的解决办法就是将li元素的点击事件绑定到它的父元素ul身上,执行事件的时候再去匹配判断目标元素。

缺点:

  • 捕获比冒泡先执行。

  • 鼠标的拖放事件,一般用捕获传递。因为这样不会使内部的元素触发处理事件,而且当鼠标down的时候且鼠标移动到document画布上时候,移动目标元素的移动事件依然可以有效。
    IE9之前IE只支持冒泡。

  • 事件管理代码有成为性能瓶颈的风险,所以尽量使它能够短小精悍。





能冒泡的事件

每个 event 都有一个event.bubbles属性,可以知道它可否冒泡。

(ref:W3定义的Event Interface)

Event TypeBubbling phase
abort
beforeinput
blur
click
compositionstart
compositionupdate
compositionend
dblclick
error
focus
focusin
focusout
input
keydown
keyup
load
mousedown
mouseenter
mouseleave
mousemove
mouseout
mouseover
mouseup
resize
scroll
select
unload
wheel

还附上了 Legacy Events(旧浏览器支持的非标准遗留事件)的 bubble 属性。

Event TypeBubbling phase
DOMActivate
DOMAttrModified
DOMCharacterDataModified
DOMFocusIn
DOMFocusOut
DOMNodeInserted
DOMNodeInsertedIntoDocument
DOMNodeRemoved
DOMNodeRemovedFromDocument
DOMSubtreeModified
keypress

H5 还定义了一些新事件:

  • media相关事件,都不冒泡
  • drag相关事件 dragstartdragdragenterdragexitdragleavedragoverdropdragend均冒泡
  • History相关事件popstatehashchange冒泡(从window开始……所以意义在哪里),pagetransition不冒泡

还有很多H5新事件,大多在草案阶段,就不一一翻开了。

此外,这里还有一个关于IE的事件列表,http://www.feiesoft.com/html/events.html

事件冒泡是我们实现事件代理(委托)的关键,在avalon1.6中,默认让能冒泡的事件都使用事件代理实现了!

let canBubbleUp = {
    click: true,
    dblclick: true,
    keydown: true,
    keypress: true,
    keyup: true,
    mousedown: true,
    mousemove: true,
    mouseup: true,
    mouseover: true,
    mouseout: true,
    wheel: true,
    mousewheel: true,
    input: true,
    change: true,
    beforeinput: true,
    compositionstart: true,
    compositionupdate: true,
    compositionend: true,
    select: true,
    cut: true,
    paste:true,
    focusin: true,
    focusout: true,
    DOMFocusIn: true,
    DOMFocusOut: true,
    DOMActivate: true,
    dragend:true,
    datasetchanged:true
}
if (!W3C) {
    delete canBubbleUp.change
    delete canBubbleUp.select
}
//....





基本实现





原生

img

可以在捕捉到的event中找到path这一条属性,这里记录了捕获阶段的路径。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

    <ul id="id-my-links">
        <li id="id_1">这是1</li>
        <li id="id_2">这是2</li>
        <li id="id_3">这是3</li>
    </ul>

</body>
<script>
    let item1 = document.getElementById("id_1");
    let item2 = document.getElementById("id_2");
    let item3 = document.getElementById("id_3");

    // item1.onclick = function () {
    //     console.log(this)
    // };
    // item2.onclick = function () {
    //     console.log(this)
    // };
    // item3.onclick = function () {
    //     console.log(this)
    // };

    let eMyLinks = document.getElementById("id-my-links")
    eMyLinks.addEventListener("click", function (event) {
        let target = event.target;
        // console.log(event);
        console.log(window.event.srcElement);
        console.log(target);

        switch (target.id) {
            case "id_1":
                console.log(this)
                break;
            case "id_2":
                console.log(this)
                break;
            case "id_3":
                console.log(this)
                break;
        }
    })
</script>

</html>


再来看另一个例子,实现鼠标移入item的时候加样式。

img

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

    <ul id="ul1">
        <li>111</li>
        <li>222</li>
        <li>333</li>
        <li>444</li>
    </ul>
    <button id="id_btn_01">添加新结点</button>
    <script type="text/javascript">
        window.onload = function () {
            let oUl = document.getElementById('ul1');
            let oBtn = document.getElementById('id_btn_01')

            let numLi = 0;

            oUl.onmouseover = function (ev) {
                console.log("当时移入:")
                console.log(event.srcElement)
                ev = ev || window.event;
                let oLi = ev.srcElement || ev.target;
                if (oLi.nodeName.toLowerCase() == 'li') {
                    oLi.style.background = 'red';
                }
            }

            oUl.onmouseout = function (ev) {
                console.log("当时移出:")
                console.log(event.srcElement)
                ev = ev || window.event;
                let oLi = ev.srcElement || ev.target;
                if (oLi.nodeName.toLowerCase() == 'li') {
                    oLi.style.background = '';
                }
            }

            oBtn.onclick = function(){
                numLi++;
                let oLi = document.createElement('li')
                oLi.innerHTML = 111*numLi;
                oUl.appendChild(oLi);
            }
        }
    </script>
</body>

</html>

小结: 精髓是下面两句。

// 正确捕捉到event
let ev = ev || window.event;

// 正确捕捉到发生event的元素
let oLi = ev.srcElement || ev.target;



jQuery事件delegate()实现事件委托

delegate() 方法为指定的元素(属于被选元素的子元素)添加一个或多个事件处理程序,并规定当这些事件发生时运行的函数。

格式:$(selector).delegate(childSelector, event, data, function)

参数描述
childSelector必需,规定要附加事件处理程序的一个或多个子元素。
event必需,规定附加到元素的一个或多个事件。由空格分隔多个事件值。必须是有效的事件。
data可选,规定传递到函数的额外数据。
function必需,规定当事件发生时运行的函数。
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <script src="http://lib.sinaapp.com/js/jquery/2.0.2/jquery-2.0.2.min.js"></script>
</head>
<body>
    <ul id="myLinks">
        <li id="goSomewhere">Go somewhere</li>
        <li id="doSomething">Do something</li>
        <li id="sayHi">Say hi</li>
    </ul>
    <script>
        $(document).ready(function () {
            $("#myLinks").delegate("#goSomewhere", "click", function () {
                location.href = "http://www.baidu.com";
            });
        });
    </script>
</body>
</html>





取消冒泡

冒泡机制有好有坏,有时候只想触发子元素的点击事件而不触发父类的点击,那么这时候就需要阻断冒泡了。

阅读如下代码。

<!DOCTYPE html>
<html>

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title></title>
    <script type="text/javascript">
        window.onload = function () {
            document.getElementById('dv').onclick = function () {
                // console.log(window.event.srcElement);
                console.log(this)
            };
            document.getElementById('p1').onclick = function () {
                // console.log(window.event.srcElement);
                console.log(this)
            }
            document.getElementById('sp').onclick = function () {
                // console.log(window.event.srcElement);
                console.log(this)
            }
        }
    </script>
</head>

<body>
    <div id="dv" style="width: 300px;height: 300px;background: yellow;">
        <p id="p1" style="width: 150px;height: 150px;background: green;">
            <span id="sp" style="background: red;">Hello!</span>
        </p>
    </div>
</body>

</html>

上面的代码中,看看event的指向在冒泡过程中会不会改变。

在这里插入图片描述

img

下面再来看看this的指向在冒泡过程中如何改变。

在这里插入图片描述

在这里插入图片描述

可以发现,event的指向在冒泡过程中,不会改变,而this指针则是冒泡到哪就指向哪个元素。

一般而言,阻止冒泡用

window.event.cancelBubble = true;	

改进后的代码如下所示。

<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
	<title></title>
	<script type="text/javascript">
		window.onload = function() {
			document.getElementById('dv').onclick = function() {
                console.log(window.event.srcElement);
                console.log(this.id);
				window.event.cancelBubble = true;	
			};
			document.getElementById('p1').onclick = function() {
                console.log(window.event.srcElement);
                console.log(this.id);
				window.event.cancelBubble = true;	
			}
			document.getElementById('sp').onclick = function() {
                console.log(window.event.srcElement);
                console.log(this.id);
				window.event.cancelBubble = true;	
			}
		}
	</script>
</head>
<body>
	<div id="dv" style="width: 300px;height: 300px;background: yellow;">
		<p id="p1" style="width: 150px;height: 150px;background: green;">
			<span id="sp" style="background: red;">Hello!</span>
		</p>
	</div>
</body>
</html>





《参考资料》

通过事件冒泡来认识 this 和 event 的区别

JavaScript事件代理(事件委托)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值