本文深入解析JavaScript中的事件代理与委托机制。通过浏览器事件冒泡原理,阐述如何利用父节点统一处理子元素事件,从而优化动态DOM操作性能并减少内存泄漏风险。同时对比jQuery和Dojo框架中的委托接口实现,并介绍纯JS环境下的代理模式应用。
在JavaScript开发中,代理(Proxy)和委托(Delegate)是两种常见且重要的设计模式。
它们具体适用于哪些场景?其底层运作原理又是怎样的?
本文将详细介绍JavaScript中delegate的具体用法与原理,并分析Dojo、jQuery等主流框架中delegate接口的实现方式。
JavaScript事件代理原理
JavaScript事件代理是前端开发中一项既实用又有趣的功能。当需要为大量元素绑定事件时,可以通过将事件监听器绑定到它们的父节点,利用事件冒泡机制将事件委托给父节点来触发处理函数。
这一特性主要得益于浏览器的事件冒泡机制。下面通过具体示例来演示如何利用该特性进行开发。
本示例参考自David Walsh的文章《How JavaScript Event Delegation Works》。
假设有一个UL父节点,其中包含多个Li子节点。
<ul id="list">
<li id="li-1">Li 1</li>
<li id="li-2">Li 2</li>
<li id="li-3">Li 3</li>
<li id="li-4">Li 4</li>
<li id="li-5">Li 5</li>
</ul>
当鼠标悬停在某个Li上时,需要获取该Li的相关信息并显示悬浮窗以展示详细信息;或者当某个Li被点击时,需要触发相应的处理逻辑。
通常的做法是为每个Li单独添加onMouseOver或onClick等事件监听器。
function addListenersLi(liElement) {
liElement.onclick = function clickHandler() {
//TODO
};
liElement.onmouseover = function mouseOverHandler() {
//TODO
}
}
window.onload = function() {
var ulElement = document.getElementById("list");
var liElements = ulElement.getElementByTagName("Li");
for (var i = liElements.length - 1; i >= 0; i--) {
addListenersLi(liElements[i]);
}
}
如果UL中的Li子元素频繁进行添加或删除操作,每次变动都需要调用addListenersLi方法为新增的Li节点重新绑定事件处理函数。
这种做法会导致添加或删除过程的复杂度增加,并提高出错的可能性。
解决该问题的方法是采用事件代理机制。当事件向上冒泡至父节点时,我们可以通过检查事件的目标对象(target)来判断并获取触发事件的源Li节点。
以下代码可以实现上述效果:
/ 获取父节点,并为它添加一个click事件
document.getElementById("list").addEventListener("click",function(e) {
// 检查事件源e.targe是否为Li
if(e.target && e.target.nodeName.toUpperCase == "LI") {
//
//TODO
console.log("List item ",e.target.id," was clicked!");
}
});
为父节点绑定一个click事件。当子节点被点击时,click事件会从子节点开始向上冒泡。父节点捕获到该事件后,通过判断e.target.nodeName来确定是否为我们需要处理的节点,并通过e.target获取被点击的Li节点,从而获取相应信息并进行处理。
事件冒泡及捕获机制
事件冒泡及捕获机制涉及浏览器对事件捕获和处理的流程。不同浏览器厂商曾有不同的处理机制,此处介绍W3C在DOM2.0标准中定义的事件模型。
DOM2.0模型将事件处理流程划分为三个阶段:
第一阶段为事件捕获阶段,
第二阶段为事件目标阶段,
第三阶段为事件起泡(冒泡)阶段。
如下图所示:

事件捕获:当某个元素触发某个事件(如onclick)时,顶层对象document会发出一个事件流,随着DOM树节点向目标元素节点传递,直到到达事件真正发生的目标元素。在此过程中,事件相应的监听函数不会被触发。
事件目标:当到达目标元素后,执行该事件相应的处理函数。若未绑定监听函数,则不执行任何操作。
事件起泡:从目标元素开始,向顶层元素传播。途中若有节点绑定了相应的事件处理函数,这些函数都会被依次触发。若需阻止事件冒泡传播,可使用e.stopPropagation()(Firefox)或e.cancelBubble=true(IE)来阻止。
jQuery和Dojo中delegate函数
接下来查看Dojo和jQuery中提供的事件代理接口的使用方法。
jQuery框架:
$("#list").delegate("li", "click", function(){
// "$(this)" is the node that was clicked
console.log("you clicked a link!",$(this));
});
jQuery的delegate方法需要三个参数:一个选择器、一个事件名称以及事件处理函数。
Dojo的实现与jQuery类似,主要区别在于两者的编程风格:
require(["dojo/query","dojox/NodeList/delegate"], function(query,delegate){
query("#list").delegate("li","onclick",function(event) {
// "this.node" is the node that was clicked
console.log("you clicked a link!",this);
});
})
Dojo的delegate模块位于dojox.NodeList中,其提供的接口与jQuery一致,参数也相同。
通过委托机制,我们可以体会到事件委托为开发带来的几个主要好处:
1. 管理的函数数量减少。无需为每个元素单独添加监听函数。对于同一父节点下类似的子元素,可以通过委托给父元素的监听函数来处理事件。
2. 可以方便地动态添加和修改元素,无需因元素的改动而重新修改事件绑定。
3. JavaScript代码和DOM节点之间的关联减少,从而降低了因循环引用导致内存泄漏的概率。
在JavaScript编程中使用代理
在JavaScript编程中使用代理 前面章节讲解了针对DOM事件处理,利用浏览器冒泡机制为DOM元素添加事件代理的方法。实际上,在纯JavaScript编程中,同样可以应用这种编程模式,通过创建代理对象来操作目标对象。
var delegate = function(client, clientMethod) {
return function() {
return clientMethod.apply(client, arguments);
}
}
var Apple= function() {
var _color = "red";
return {
getColor: function() {
console.log("Color: " + _color);
},
setColor: function(color) {
_color = color;
}
};
};
var a = new Apple();
var b = new Apple();
a.getColor();
a.setColor("green");
a.getColor();
//调用代理
var d = delegate(a, a.setColor);
d("blue");
//执行代理
a.getColor();
//b.getColor();
上述示例介绍的是利用浏览器冒泡机制为DOM元素添加事件代理。实际上,在纯JS编程中,我们也可以采用这种编程模式,创建代理对象来操作目标对象。
在上面的例子中,通过调用delegate()函数创建的代理函数d来操作对a的修改。
这种方式虽然使用了apply(或call)来实现调用对象的转移,但从编程模式上实现了对某些对象的隐藏,可以保护这些对象不被随意访问和修改。
在许多框架中都引用了委托概念,用于指定方法的运行作用域。
比较典型的如dojo.hitch(scope,method)和ExtJS的createDelegate(obj,args)。以上就是本文的全部内容,希望对大家学习JavaScript程序设计有所帮助。