事件处理器:onclick、onmouseover....
在传统的事件处理中,你需要为每一个元素添加或者是删除事件处理器。然而,事件处理器将有可能导致内存泄露或者是性能下降——你用得越多这种风险就越大。
JavaScript 事件代理:当我们需要对很多元素添加事件的时候,可以通过将事件添加到它们的父节点而将事件委托给父节点来触发处理函数。这主要得益于浏览器的事件冒泡机制
事件代理用到了两个特性:事件冒泡以及目标元素。
事件冒泡:当一个元素上的事件被触发的时候,比如说鼠标点击了一个按钮,同样的事件将会在那个元素的所有祖先元素中被触发。这一过程被称为事件冒泡;这个事件从原始元素开始一直冒泡到 DOM 树的最上层。
目标元素:任何一个事件的目标元素都是最开始的那个元素,也就是我们出发的元素。在我们的这个例子中也就是按钮。
使用事件代理:把事件处理器添加到一个元素上,等待一个事件从它的子级元素里冒泡上来,并且可以得知这个事件是从哪个元素开始的。
我们所要关心的只是如何检测目标元素而已。比方说我们有一个 table 元素,ID 是 "report",我们为这个表格添加一个事件处理器以调用 editCell 函数。editCell 函数需要判断传到 table 来的事件的目标元素。考虑到我们要写的几个函数中都有可能用到这一功能,所以我们把获取目标元素单独放到一个名为 getEventTarget 的函数中:
- function getEventTarget(e) {
- //event属性兼容写法
- e = e || window.event;
- //获取目标元素兼容写法
- return e.target || e.srcElement;
- }
接下来就是 editCell 函数了,这个函数调用到了 getEventTarget 函数。一旦我们得到了目标元素,剩下的事情就是看看它是否是我们所需要的那个元素了。
- function editCell(e) {
- var target = getEventTarget(e);
- if (target.tagName.toLowerCase() == 'td') {
- // DO SOMETHING WITH THE CELL
- }
- }
之前的介绍中已经说到了浏览器的事件冒泡机制。这里再详细介绍一下浏览器处理 DOM 事件的过程。对于事件的捕获和处理,不同的浏览器厂商有不同的处理机制,这里我们主要介绍 W3C 对 DOM2.0 定义的标准事件。
DOM2.0 模型将事件处理流程分为三个阶段:一、事件捕获阶段,二、事件目标阶段,三、事件起泡阶段。如图:
事件捕获:当某个元素触发某个事件(如 onclick),顶层对象 document 就会发出一个事件流,随着 DOM 树的节点向目标元素节点流去,直到到达事件真正发生的目标元素。在这个过程中,事件相应的监听函数是不会被触发的。
事件目标:当到达目标元素之后,执行目标元素该事件相应的处理函数。如果没有绑定监听函数,那就不执行。
事件起泡:从目标元素开始,往顶层元素传播。途中如果有节点绑定了相应的事件处理函数,这些函数都会被一次触发。如果想阻止事件起泡,可以使用 e.stopPropagation()(Firefox)或者 e.cancelBubble=true(IE)来组织事件的冒泡传播。
假设有一个 UL 的父节点,包含了很多个 Li 的子节点:
- <ul id="parent-list">
- <li id="post-1">
- Item 1
- </li>
- <li id="post-2">
- Item 2
- </li>
- <li id="post-3">
- Item 3
- </li>
- <li id="post-4">
- Item 4
- </li>
- <li id="post-5">
- Item 5
- </li>
- <li id="post-6">
- Item 6
- </li>
- </ul>
我们通常的做法是对每一个元素进行循环操作添加监听事件:
- var oUl = document.getElementById("parent-list");
- var aLi = oUl.getElementsByTagName('li');
- for (var i = 0; i < aLi.length; i++) {
- aLi[i].onclick = function() {
- console.log(this.id);
- }
- }
如果这个 UL 中的 Li 子元素会频繁地添加或者删除,我们就需要在每次添加 Li 添加事件处理函数,这就增加了复杂度和出错的可能性。
更简单的方法是使用事件代理机制,当事件被抛到更上层的父节点的时候,我们通过检查事件的目标对象(target)来判断并获取事件源 Li。下面的代码可以完成我们想要的效果:
- var oUl = document.getElementById("parent-list");
- var aLi = oUl.getElementsByTagName('li');
- function target(e) {
- var oEvent = e || event;
- return oEvent.target || oEvent.srcElement;
- }
- oUl.addEventListener('click',
- function(e) {
- var oEvent = e || event;
- var targets = target(oEvent);
- if (targets.tagName.toLowerCase() == 'li') {
- console.log(targets.id);
- }
- })
为父节点添加一个 click 事件,当子节点被点击的时候,click 事件会从子节点开始向上冒泡。父节点捕获到事件之后,通过判断 e.target.nodeName 来判断是否为我们需要处理的节点。并且通过 e.target 拿到了被点击的 Li 节点。从而可以获取到相应的信息,并作处理。
下面看一下 jQuery 中提供的事件代理接口的使用方法。
- $("#link-list").delegate("a", "click",
- function() {
- // "$(this)" is the node that was clicked
- console.log("you clicked a link!", $(this));
- });
jQuery 的 delegate 的方法需要三个参数,一个选择器,一个事件名称,和事件处理函数。
通过上面的介绍,大家应该能够体会到使用事件委托对于 web 应用程序带来的几个优点:
1. 管理的函数变少了。不需要为每个元素都添加监听函数。对于同一个父节点下面类似的子元素,可以通过委托给父元素的监听函数来处理事件。
2. 可以方便地动态添加和修改元素,不需要因为元素的改动而修改事件绑定。
3.JavaScript 和 DOM 节点之间的关联变少了,这样也就减少了因循环引用而带来的内存泄漏发生的概率。
潜在的问题也许并不那么明显,但是一旦你注意到这些问题,你就可以轻松地避免它们:
你的事件管理代码有成为性能瓶颈的风险,所以尽量使它能够短小精悍。
不是所有的事件都能冒泡的。blur、focus、load 和 unload 不能像其它事件一样冒泡。事实上 blur 和 focus 可以用事件捕获而非事件冒泡的方法获得(在 IE 之外的其它浏览器中)。
在管理鼠标事件的时候有些需要注意的地方。如果你的代码处理 mousemove 事件的话你遇上性能瓶颈的风险可就大了,因为 mousemove 事件触发非常频繁。而 mouseout 则因为其怪异的表现而变得很难用事件代理来管理。
上面介绍的是对 DOM 事件处理时,利用浏览器冒泡机制为 DOM 元素添加事件代理。其实在纯 JS 编程中,我们也可以使用这样的编程模式,来创建代理对象来操作目标对象。这里引用相关中的一个例子:
- var delegate = function(client, clientMethod) {
- return function() {
- return clientMethod.apply(client, arguments);
- }
- }
- var ClassA = function() {
- var _color = "red";
- return {
- getColor: function() {
- console.log("Color: " + _color);
- },
- setColor: function(color) {
- _color = color;
- }
- };
- };
- var a = new ClassA();
- a.getColor();
- a.setColor("green");
- a.getColor();
- console.log("执行代理!");
- var d = delegate(a, a.setColor);
- d("blue");
- console.log("执行完毕!");
- a.getColor();
上面的例子中,通过调用 delegate() 函数创建的代理函数 d 来操作对 a 的修改。这种方式尽管是使用了 apply(call 也可以)来实现了调用对象的转移,但是从编程模式上实现了对某些对象的隐藏,可以保护这些对象不被随便访问和修改。
在很多框架中都引用了委托这个概念用来指定方法的运行作用域。比较典型的如 dojo.hitch(scope,method) 和 ExtJS 的 createDelegate(obj,args)。有兴趣的同学可以看一下他们的源代码,主要也是 js 函数的 apply 方法来制定执行作用域。(其实我对于上面这个栗子,不是特别理解是如何运用的事件代理,能够深刻理解的童鞋请帮我留言讲解一下,谢谢)
已经有一些使用主流类库的事件代理示例出现了,比如说 jQuery、Prototype 以及 Yahoo! UI。你也可以找到那些不用任何类库的例子,比如说 Usable Type blog 上的这一个。一旦需要的话,事件代理将是你工具箱里的一件得心应手的工具,而且它很容易实现。
来源: http://www.cnblogs.com/yuqingfamily/p/5837930.html