理解Javascript中的事件绑定与事件委托

最近在深入实践js中,遇到了一些问题,比如我需要为动态创建的DOM元素绑定事件,那么普通的事件绑定就不行了,于是通过上网查资料了解到事件委托,因此想总结一下js中的事件绑定与事件委托。

事件绑定

 

最直接的事件绑定:HTML事件处理程序

如下示例代码,通过节点属性显式声明,直接在HTML中,显式地为按钮绑定了click事件,当该按钮有用户点击行为时,便会触发myClickFunc方法。

/* html */

<button id="btn" onclick="myClickFunc()">

ClickMe

</button>

/* js */

// 事件处理程序

var myClickFunc = function(evt){

// TODO..

};

// 移除事件处理程序

myClickFunc = function(){};

显而易见,这种绑定方式非常不友好,HTML代码和JS代码严重耦合在一起,比如当要修改一个函数名时候,就要修改两次,

DOM 0 级事件处理程序

通过DOM操作动态绑定事件,是一种比较传统的方式,把一个函数赋值给事件处理程序。这种方式也是应用较多的方式,比较简单。看下面例子:

/* html */

<button id="btn">ClickMe</button>

/* js */

// 事件处理程序

var myClickFunc = function(evt){

// TODO ...

};

// 直接给DOM节点的 onclick 方法赋值,注意这里接收的是一个function

document.getElementById(‘btn‘).onclick = myClickFunc;

// 移除事件处理程序

document.getElementById(‘btn‘).onclick = null;

 

DOM 2 级事件处理程序

通过事件监听的方式绑定事件,DOM2级事件定义了两个方法,用于处理指定和删除事件处理程序的操作。

// event: 事件名称

// function: 事件函数

// boolean: false | true, true 为事件捕获, false 为事件冒泡(默认);

Ele.addEventListener(event,function[,boolean]); // 添加句柄

ELe.removeEventListener(event,function[,boolean]); // 移除句柄

看个例子:

/* html */

<button id="btn">ClickMe</button>

/* js */

// 通过DOM操作进行动态绑定:

// 获取btnHello节点

var oBtn = document.getElementById(‘btn‘);

// 增加第一个 click 事件监听处理程序

oBtn.addEventListener(‘click‘,function(evt){

// TODO sth 1...

});

// 增加第二个 click 事件监听处理程序

oBtn.addEventListener(‘click‘,function(evt){

// TODO sth 2...

});

// ps:通过这种形式,可以给btn按钮绑定任意多个click监听;注意,执行顺序与添加顺序相关。

// 移除事件处理程序

oBtn.removeEventListener(‘click‘,function(evt){..});

 

IE事件处理程序

DOM 2级事件处理程序在IE是行不通的,IE有自己的事件处理程序方法:attachEvent()和detachEvent()。这两个方法的用法与addEventListener()是一样的,但是只接收两个参数,一个是事件名称,另一个是事件处理程序的函数。为什么不使用第三个参数的原因呢?因为IE8以及更早的浏览器版本只支持事件冒泡。看个例子:

/* html */

<button id="btn">ClickMe</button>

/* js */

var oBtn = document.getElementById(‘btn‘);

// 事件处理函数

function evtFn(){

console.log(this);

}

// 添加句柄

oBtn.attachEvent(‘onclick‘,evtFn);

// 移除句柄

oBtn.detachEvent(‘onclick‘,evtFn);

 

简易的跨浏览器解决方法

如果我们既要支持IE的事件处理方法,又要支持 DOM 2级事件,那么就要封装一个跨浏览器的事件处理函数,如果支持 DOM 2级事件,就用addEventListener,否则就用attachEvent。例子如下:

//跨浏览器事件处理程序

var eventUtil = {

// 添加句柄

addHandler: function(element, type, handler){

if(element.addEventListener){

element.addEventListener(type, handler, false);

}else if(element.attachEvent){

element.attachEvent(‘on‘ + type, handler);

}else{

element[‘on‘ + type] = handler;

}

},

// 删除句柄

removeHandler: function(element, type, handler){

if(element.removeEventListener){

element.removeEventListener(type, handler, false);

}else if(element.detachEvent){

element.detachEvent(‘on‘ + type, handler);

}else{

element[‘on‘ + type] = null;

}

}

};

var oBtn = document.getElementById(‘btn‘);

function evtFn(){

alert(‘hello world‘);

}

eventUtil.addHandler(oBtn, ‘click‘, evtFn);

eventUtil.removeHandler(oBtn, ‘click‘, evtFn);

 

事件冒泡和事件捕获

在了解事件委托之前,要先了解下事件冒泡和事件捕获。

早期的web开发,浏览器厂商很难回答一个哲学上的问题:当你在页面上的一个区域点击时,你真正感兴趣的是哪个元素。这个问题带来了交互的定义。在一个元素的界限内点击,显得有点含糊。毕竟,在一个元素上的点击同时也发生在另一个元素的界限内。例如单击一个按钮。你实际上点击了按钮区域、body元素的区域以及html元素的区域。

伴随着这个问题,两种主流的浏览器Netscape和IE有不同的解决方案。Netscape定义了一种叫做事件捕获的处理方法,事件首先发生在DOM树的最高层对象(document)然后往最深层的元素传播。在图例中,事件捕获首先发生在document上,然后是html元素,body元素,最后是button元素。

IE的处理方法正好相反。他们定义了一种叫事件冒泡的方法。事件冒泡认为事件促发的最深层元素首先接收事件。然后是它的父元素,依次向上,知道document对象最终接收到事件。尽管相对于html元素来说,document没有独立的视觉表现,他仍然是html元素的父元素并且事件能冒泡到document元素。所以图例中噢噢那个button元素先接收事件,然后是body、html最后是document。如下图:

 

事件冒泡

简单点说,事件冒泡就是事件触发时,会从目标DOM元素向上传播,直到文档根节点,一般情况下,会是如下形式传播:

targetDOM → parentNode → ... → body → document → window

如果希望一次事件触发能在整个DOM树上都得到响应,那么就需要用到事件冒泡的机制。看下面示例:

/* html */

<button id="btn">ClickMe</button>

/* js */

// 给按钮增加click监听

document.getElementById(‘btn‘).addEventListener(‘click‘,function(evt){

alert(‘button clicked‘);

},false);

// 给body增加click监听

document.body.addEventListener(‘click‘,function(evt){

alert(‘body clicked‘);

},false);

在这种情况下,点击按钮“ClickMe”后,其自身的click事件会被触发,同时,该事件将会继续向上传播, 所有的祖先节点都将得到事件的触发命令,并立即触发自己的click事件;所以如上代码,将会连续弹出两个alert.

在有些时候,我们想让事件独立触发,所以我们必须阻止冒泡,用event的stopPropagation()方法。

<button id="btn">ClickMe</button>

/* js */

// 给按钮增加click监听

document.getElementById(‘btn‘).addEventListener(‘click‘,function(evt){

alert(‘button clicked‘);

evt.stopPropagation(); //阻止事件冒泡

},false);

// 给body增加click监听

document.body.addEventListener(‘click‘,function(evt){

alert(‘body clicked‘);

},false);

此时,点击按钮后,只会触发按钮本身的click事件,得到一个alert效果;该按钮的点击事件,不会向上传播,body节点就接收不到此次事件命令。

需要注意的是:

  1. 不是所有的事件都能冒泡,如:blur、focus、load、unload都不能
  2. 不同的浏览器,阻止冒泡的方式也不一样,在w3c标准中,通过event.stopPropagation()完成, 在IE中则是通过自身的event.cancelBubble=true来完成。

 

事件委托

事件委托看起来挺难理解,但是举个生活的例子。比如,有三个同事预计会在周一收到快递。为签收快递,有两种办法:一是三个人在公司门口等快递;二是委托给前台MM代为签收。现实当中,我们大都采用委托的方案(公司也不会容忍那么多员工站在门口就为了等快递)。前台MM收到快递后,她会判断收件人是谁,然后按照收件人的要求签收,甚至代为付款。这种方案还有一个优势,那就是即使公司里来了新员工(不管多少),前台MM也会在收到寄给新员工的快递后核实并代为签收。举个例子

HTML结构:

<ul id="ul-item">

<li>item1</li>

<li>item2</li>

<li>item3</li>

<li>item4</li>

</ul>

如果我们要点击li标签,弹出里面的内容,我们就需要为每个li标签绑定事件。

(function(){

var oUlItem = document.getElementById(‘ul-item‘);

var oLi = oUlItem.getElementsByTagName(‘li‘);

for(var i=0, l = oLi.length; i < l; i++){

oLi[i].addEventListener(‘click‘,show);

};

function show(e){

e = e || window.event;

alert(e.target.innerHTML);

};

})();

虽然这样子能够实现我们想要的功能,但是如果这个UL中的LI子元素频繁的添加或删除,我们就需要在每次添加LI的时候为它绑定事件。这就添加了复杂度,并且造成内存开销较大。

更简单的方法是利用事件委托,当事件被掏到更上层的父节点的时候,通过检查事件的目标对象(target)来判断并获取事件源LI。

(function(){

var oUlItem = document.getElementById(‘ul-item‘);

oUlItem.addEventListener(‘click‘,show);

function show(e){

e = e || window.event;

var src = e.target;

if(src && src.nodeName.toLowerCase() === ‘li‘){

alert(src.innerHTML);

}

}

})();

这里我们为父节点UL添加了点击事件,当点击子节点LI标签的时候,点击事件会冒泡到父节点。父节点捕获到事件之后,通过判断e.target.nodeName来判断是否为我们需要处理的节点,并且通过e.target拿到了被点击的Li节点。从而可以获取到相应的信息,并做处理。

优点:

通过上面的介绍,大家应该能够体会到使用事件委托对于web应用程序带来的几个优点:

  1. 管理的函数变少了。不需要为每个元素都添加监听函数。对于同一个父节点下面类似的子元素,可以通过委托给父元素的监听函数来处理事件。
  2. 可以方便地动态添加和修改元素,不需要因为元素的改动而修改事件绑定。
  3. JavaScript和DOM节点之间的关联变少了,这样也就减少了因循环引用而带来的内存泄漏发生的概率。

参考资料

http://www.diguage.com/archives/71.html

http://owenchen.net/?p=15

转自: https://segmentfault.com/a/1190000006667581

作者: Allin

时间: 2024-10-11 01:26:10

理解Javascript中的事件绑定与事件委托的相关文章

JS中的事件绑定,事件捕获,事件冒泡以及事件委托,兼容IE

转载请注明出处:http://www.cnblogs.com/zhangmingze/p/4864367.html ● 事件分为三个阶段:   事件捕获 -->  事件目标 -->  事件冒泡 ● 事件捕获:事件发生时(onclick,onmouseover--)首先发生在document上,然后依次传递给body.……最后到达目的节点(即事件目标). ● 事件冒泡:事件到达事件目标之后不会结束,会逐层向上冒泡,直至document对象,跟事件捕获相反 1.onlick -->事件冒泡,

javascript中的常用表单事件用法

下面介绍几种javascript中常用的表单事件: 一,onsubmit:表单中的确认按钮被点击时发生的事件,如下案例. 案例解析:弹出表单中提交的内容 <form name="testform" action="#" id="form"> What is your name?<br /> <input type="text" name="fname" /> <in

【干货理解】理解javascript中实现MVC的原理

理解javascript中的MVC MVC模式是软件工程中一种软件架构模式,一般把软件模式分为三部分,模型(Model)+视图(View)+控制器(Controller); 模型:模型用于封装与应用程序的业务逻辑相关的数据以及对数据处理的方法.模型有对数据直接访问的权利.模型不依赖 "视图" 和 "控制器", 也就是说 模型它不关心页面如何显示及如何被操作. 视图:视图层最主要的是监听模型层上的数据改变,并且实时的更新html页面.当然也包括一些事件的注册或者aja

js架构设计模式——理解javascript中的MVVM开发模式

理解javascript中的MVVM开发模式 http://blog.csdn.net/slalx/article/details/7856769 MVVM的全称是Model View ViewModel,这种架构模式最初是由微软的MartinFowler作为微软软件的展现层设计模式的规范提出,它是MVC模式的衍生物,MVVM模式的关注点在能够支持事件驱动的UI开发平台,例如HTML5,[2][3] WindowsPresentation Foundation (WPF), Silverligh

转 。。理解javascript中的MVVM开发模式

MVVM的全称是Model View ViewModel,这种架构模式最初是由微软的MartinFowler作为微软软件的展现层设计模式的规范提出,它是MVC模式的衍生物,MVVM模式的关注点在能够支持事件驱动的UI开发平台,例如HTML5,[2][3] WindowsPresentation Foundation (WPF), Silverlight 和 t ZK framework,Adobe Flex. 对这种模式的实现,大部分都是通过在view层声明数据绑定来和其他层分离的,这样就方便了

理解JavaScript中函数的使用

函数是进行模块化程序设计的基础,编写复杂的Ajax应用程序,必须对函数有更深入的了解. JavaScript中的函数不同于其他的语言,每个函数都是作为一个对象被维护和运行的.通过函数对象的性质,可以很方便的将一个函数赋值给一个变量或者将函数作为参数传递.在继续讲述之前,先看一下函数的使用语法: function func1(…){…} var func2=function(…){…}; var func3=function func4(…){…}; var func5=new Function(

理解 JavaScript 中的 this

前言 理解this是我们要深入理解 JavaScript 中必不可少的一个步骤,同时只有理解了 this,你才能更加清晰地写出与自己预期一致的 JavaScript 代码. 本文是这系列的第三篇,往期文章: 理解 JavaScript 中的作用域 理解 JavaScript 中的闭包 什么是 this 消除误解 在解释什么是this之前,需要先纠正大部分人对this的误解,常见的误解有: 指向函数自身. 指向它所在的作用域. 关于为何会误解的原因这里不多讲,这里只给出结论,有兴趣可以自行查询资料

JavaScript大杂烩6 - 理解JavaScript中的this

在JavaScript开发中,this是很常用的一个关键字,但同时也是一个很容易引入bug的一个关键字,在这里我们就专门总结一下页面中可能出现的this关键字(包括几种在其他页面文件中出现的this). JavaScript中的this关键字通常只使用在函数中,它指向当前函数的调用者,这是this关键字的本质,所有的使用方式都是围绕这个展开的,让我们来看一下在各种性质的函数中this的用法.1. 在对象的函数中使用this var person = { name: 'Frank', say: f

全面理解Javascript中Promise

全面理解Javascript中Promise 最近在学习Promise的时候,在网上收集了一些资料,发现很多的知识点不够系统,所以小编特意为大家整理了一些自认为 比较好的文章,供大家更好地学习js中非常有趣的Promise Promise概念 2015 年 6 月,ECMAScript 6 的正式版 终于发布了. ECMAScript 是 JavaScript 语言的国际标准,javascript 是 ECMAScript 的实现.ES6 的目标,是使得 JavaScript 语言可以用来编写大