初步理解JS的事件机制

一、事件流(捕获,冒泡)

事件流:指从页面中接收事件的顺序,有冒泡流和捕获流。

当页面中发生某种事件(比如鼠标点击,鼠标滑过等)时,毫无疑问子元素和父元素都会接收到该事件,可具体顺序是怎样的呢?冒泡和捕获则描述了两种不同的顺序。

DOM2级事件规定事件流包括三个阶段,如图:

假如我们点击一个div, 实际上是先点击document,然后点击事件传递到div,而且并不会在这个div就停下,div有子元素就还会向下传递,最后又会冒泡传递回document,如上图

为了兼容更多的浏览器,非特殊情况一般我们都是把事件添加到在事件冒泡阶段。

二、事件处理程序

DOM0级事件处理程序

例子:

1 var btn5 = document.getElementById(‘btn5‘);
2 btn5.onclick=function(){
3    console.log(this.id);//btn5
4 };

注意:基于DOM0的事件,对于同一个dom节点而言,只能注册一个,后边注册的 同种事件 会覆盖之前注册的。利用这个原理我们可以解除事件,btn5.onclick=null;其中this就是绑定事件的那个元素;

以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理;

DOM2级事件处理程序

DOM2支持同一dom元素注册多个同种事件,事件发生的顺序按照添加的顺序依次触发(IE是相反的)。

DOM2事件通过addEventListener和removeEventListener管理

//addEventListener(eventName,handlers,boolean);removeEventListener(),两个方法都一样接收三个参数,第一个是要处理的事件名,第二个是事件处理程序,第三个值为false时表示在事件冒泡阶段调用事件处理程序,一般建议在冒泡阶段使用,特殊情况才在捕获阶段;

注意:通过addEventListener()添加的事件处理程序只能用removeEventListener()来移除,并且移除时传入的参数必须与添加时传入的参数一样;比如

例子:

1 var btn2 = document.getElementById(‘btn2‘);
2 var handlers = function () {
3    console.log(this.id);
4 };
5
6 btn2.addEventListener(‘click‘,handlers,false);
7
8 btn2.removeEventListener(‘click‘,handlers.false);

IE事件处理程序

//IE事件处理程序(IE和Opera支持)
//IE用了attachEvent(),detachEvent(),接收两个参数,事件名称和事件处理程序,通过attachEvent()添加的事件处理程序都会被添加到冒泡阶段,所以平时为了兼容更多的浏览器最好将事件添加到事件冒泡阶段,IE8及以前只支持事件冒泡;

例子:

1 var btn3 = document.getElementById(‘btn3‘);
2 var handlers2=function(){
3    console.log(this===window);//true,注意attachEvent()添加的事件处理程序运行在全局作用域中;
4 };
5 btn3.attachEvent(‘onclick‘,handlers2);

跨浏览器事件处理程序

 1 //创建的方法是addHandlers(),removeHandlers(),这两个方法属于一个叫EventUtil的对象;但是这个没有考虑到IE中作用域的问题,不过就添加和移除事件还是足够的。
 2
 3 var EventUtil = {
 4    addHandlers: function (element, type, handlers) {
 5       if (element.addEventListener) {
 6          element.addEventListener(type, handlers, false);
 7       } else if (element.attachEvent) {
 8          element.attachEvent(on + type, handlers);
 9       } else {
10          element[‘on‘ + type] = handlers;
11       }
12    },
13    removeHandlers: function (element, type, handlers) {
14       if (element.removeEventListener) {
15          element.removeEventListener(type, handlers, false);
16       } else if (element.detachEvent) {
17          element.detachEvent(on + type, handlers);
18       } else {
19          element[‘on‘ + type] = null;
20       }
21    }
22 };

例子:

1 var btn4=document.getElementById(‘btn4‘);
2 var handlers3=function(){
3    console.log(‘123‘)
4 };
5 EventUtil.addHandlers(btn4,‘click‘,handlers3);
6 //……
7 EventUtil.removeHandlers(btn4,‘click‘,handlers3); 

在同一个对象上注册事件,并不一定按照注册顺序执行,冒泡或捕获模式会影响其被触发的顺序;

三、事件对象

兼容触发DOM上的某个事件时,会产生一个事件对象event,这个对象中包含了所有与事件有关的信息,比如导致事件的元素target,事件的类型,及其他特定的相关信息。例如鼠标操作导致的事件对象中会包含鼠标的位置,单双击等,而键盘操作导致的事件对象会包含按下的键等信息;

事件被触发时,会默认给事件处理程序传入一个参数e , 表示事件对象;通过e,我们可以获得其中包含的与事件有关的信息;

只有在事件处理程序执行期间,event对象才会存在,一旦事件处理程序执行完毕,event对象就会被销毁;

DOM中的事件对象

兼容DOM的浏览器会自动将一个事件对象event传递给事件处理程序

ps:关于事件对象中的this,target,currentTarget,看个例子:(注:event.target不支持IE浏览器,应该用event.srcElement;还有 IE中通过attachment添加的事件是运行在全局作用域中的,this===window)

当事件绑定在真正的目标元素上时,this===target===currentTarget ,而且绑定事件时是否捕获结果都是一样的,此时eventParse==2;

 1 //this,target,currentTarget,this===currentTarget
 2 $(‘#outer‘).on(‘click‘,‘#center‘,function(e){
 3    console.log(this.id);//on()中间的参数是个过滤器,相当于将事件绑定到了#center上;此时点击#center将不会触发事件
 4    console.log(e.target.id);
 5    console.log(e.currentTarget.id);
 6 });
 7
 8 $(‘#outer‘).on(‘click‘,function(e){
 9    console.log(this.id);
10    console.log(e.target.id);
11    console.log(e.currentTarget.id);
12 });
13
14 event.stopPropagation()不能简单说阻止了事件的冒泡,其实也阻止了事件的继续捕获,确切的说应该是阻止事件的进一步传播
15
16 var d1 = document.getElementById(‘d1‘);
17 d1.addEventListener(‘click‘, function (evt) {
18     console.log(‘d1‘);
19     evt.stopPropagation();
20 }, true);
21 var d2 = document.getElementById(‘d2‘);
22 d2.addEventListener(‘click‘, function () {
23     console.log(‘d2‘);
24 }, true);

输出结果是:s1;

event.stopPropagation()可以组织事件的传播,但它阻止不了绑定在该元素上的其他函数的执行,比如将上面例子改一下,给d1再绑定一个事件,同时d1的第一个事件中改成event.stopImmediatePropagation(),那么第二个事件也会被阻止,它不仅阻止事件的传播还阻止后续事件的执行;

 1 var d1 = document.getElementById(‘d1‘);
 2 d1.addEventListener(‘click‘, function (evt) {
 3     console.log(‘d1‘);
 4     evt.stopImmediatePropagation();
 5 }, true);
 6 d1.addEventListener(‘click‘, function (evt) {
 7     console.log(‘d1+1‘);
 8 }, true);
 9 var d2 = document.getElementById(‘d2‘);
10 d2.addEventListener(‘click‘, function () {
11     console.log(‘d2‘);
12 }, true);

IE中的事件对象

IE中event参数是未定的,事件对象是作为window的一个属性存在的,因此可以通过window.event来访问event对象,不同于DOM级中event是作为参数直接传入和返回;

事件函数是以on开头的;

属性上也有一些不同,如下:

跨浏览器的事件对象

虽然DOM和IE中对象不同,但是两者event中的全部信息和方法都是类似的只是实现方式不同,可以用前面提到过的EventUtil对象来求同存异

 1 var EventUtil = {
 2     addHandler: function (element, type, handler) {
 3         if (element.addEventListener) {
 4             element.addEventListener(type, handler, false);
 5         } else if (element.attachEvent) {
 6             element.attachEvent(on + type, handler);
 7         } else {
 8             element[‘on‘ + type] = handler;
 9         }
10     },
11
12     getEvent: function (event) {
13         return event ? event : window.event;
14
15     },
16
17     getTarget: function (event) {
18         return event.target || event.srcElement;
19     },
20
21     preventDefault: function (event) {
22         if (event.preventDefault) {
23             event.preventDefault();
24         } else {
25             event.returnValue = false;
26         }
27     },
28
29     stopPropagation: function (event) {
30         if (event.stopPropagation) {
31             event.stopPropagation();
32         } else {
33             event.cancelBubble = true;
34         }
35     },
36
37     removeHandler: function (element, type, handler) {
38         if (element.removeEventListener) {
39             element.removeEventListener(type, handler, false);
40         } else if (element.detachEvent) {
41             element.detachEvent(on + type, handler);
42         } else {
43             element[‘on‘ + type] = null
44         }
45     }
46
47 };

使用上面的EventUtil对象,举个例子:

1 var myBtn=document.getElementById(‘my-btn‘);
2 btn.onclick=function(event){
3     event=EventUtil.getEvent(event);
4     EventUtil.preventDefault(event);
5 };

个例子:必须要假设有一个事件对象event传入了事件处理程序中,要使用EventUtil中的方法需要将该事件对象传给那些方法,该事件对象则需要通过其getEvent方法来获得;

四、事件委托

每当将事件处理程序制定给元素时,运行中的浏览器代码与支持页面交互的JS代码之间就会建立一个连接,而这种连接越多,页面执行起来就越慢。考虑内存和性能问题,为了解决事件处理程序过多的问题,采用事件委托变得很有必要。(考虑到内存,也应该尽量减少不必要的事件处理程序,对于内存中过时不用的’空事件处理程序’,也是很有必要将其移除的;)

因为冒泡机制,比如既然点击子元素,也会触发父元素的点击事件,那我们完全可以将子元素的事件要做的事写到父元素的事件里,也就是将子元素的事件处理程序写到父元素的事件处理程序中,这就是事件委托;利用事件委托,只指定一个事件处理程序,就可以管理某一个类型的所有事件;例如:

 1 var myLinks=document.getElementById(‘my-links‘);
 2 myHandlers=function(event){
 3     event=EventUtil.getEvent(event);
 4     var target=EventUtil.getTarget(event);
 5
 6     switch(target.id){
 7         case ‘item-1‘:
 8             location.href=‘http://www.cnblogs.com/lazychen/‘;
 9             break;
10         case ‘item-2‘:
11             document.title=‘event‘;
12             break;
13         case ‘item-3‘:
14             console.log(‘hi‘);
15             break;
16     }
17 };
18 EventUtil.addHandler(myLinks,‘click‘,myHandlers);

点击任何一个 li ,该点击事件依然会冒泡到父元素 ul 上,所以直接将点击 li 后要做的事写到了父元素的点击事件里;

《JS高设 3rd》中列出了几种最适合采用事件委托的事件:click,mousedown,mouseup,keydown,keyup,keypress

总结一下js委托相关的:

  • 因为把事件绑定到了父节点上,因此省了绑定事件。就算后面新增的子节点也有了相关事件,删除部分子节点不用去销毁对应节点上绑定的事件
  • 父节点是通过event.target来找对应的子节点的。(事件处理程序中的this值始终等于currentTarget的值,指向的是绑定到的那个元素)

ps:文中盗了两张图~大大莫怪莫怪

时间: 2024-08-03 11:15:06

初步理解JS的事件机制的相关文章

深入理解js Dom事件机制(二)——添加事件处理程序

深入理解js Dom事件机制(一)--事件流 事件就是当用户或者浏览器自身执行的某种动作,诸如 click.mouseover等都是事件的名称,那响应个事件的函数就称为事件处理程序(事件处理函数.事件句柄). 事件处理程序的名字都是以on+事件名称命名,比如 click事件的事件处理程序就是onclick, 为某个事件指定事件处理程序的方式大致分为三种. 1.HTML事件处理程序 这个很简单,大家基本初学js的时候都应该用过,就不再赘述,直接看实例代码 <!DOCTYPE html> <

js的事件机制二

js的事件机制二 1.给合适的HTML标签添加合适的事件 onchange-----select下拉框 onload-----body标签 单双击-----用户会进行点击动作的HTML元素 鼠标事件 ---用户会进行鼠标移动的操作的 键盘事件----用户会进行键盘操作的HTML元素 2.给HTML添加多个事件时,注意事件的冲突 举个例子:单击和双击 当事件的触发条件包含相同部分时候,会产生事件之间的冲突. 3.事件的阻断 当事件所监听的函数的将返回值返回给事件时 false:则会阻断当前所在HT

我也来说说js的事件机制

原文链接:http://www.w3cfuns.com/notes/17398/8062de2558ef495ce6cb7679f940ae5c.html 学js,不懂事件机制,基本可以说学了js,就是白学.本人看了很多js相关书籍,评价一本说讲得好不好,我主要看两块儿,一块儿是js面向对象讲得怎么样,另一块儿就是这个事件机制这块儿.面向对象按下不表,这里就详细说说事件机制.事件这个东西可以说js中核心之一.为啥如此重要,因为js是一门事件驱动的语言. 说说本文的结构.(真的好长,又不想写成一个

node.js 的事件机制

昨天到今天, 又看了一边node 的事件模块,  觉得很神奇~  分享一下  - -> 首先, 补充下对node 的理解: nodeJs 是一个单进程单线程应用程序, 但是通过事件和回调支持并发, 所以性能非常高~ 那么什么是单进程单线程呢~(写给语文跟我一样不好的小伙伴) 我们来看下单进程和多进程的区别: 1.  多进程的优势在于任务的独立性,比如某个任务单独作为一个进程的话,崩溃只影响自己的服务,其他任务不受影响.如果是多个任务在同一个进程内部利用多个线程进行处理,某个线程发生了未处理的异常

C#学习之初步理解委托、事件、匿名方法和Lambda

最经在学习LinqtoSql,然后扯到Lambda表达式,然后扯到匿名方法,然后扯到委托,最后扯到事件处理...后来发现对委托这个概念和事件处理这个过程理解得不是很清晰,遂得一下学习笔记.那里说得不对,请大家多多指教! 第一部分:理解委托 委托委托,顾名思义,就是类似于中间人的意思,有些事情你不做,委托别人去做,比如你想相亲,但你不想去主动约女孩子,那你可以交给媒婆去帮你约. 如果你学过C++,请把委托理解成函数指针,都是为了调用函数.函数指针可以调用符合该函数指针要求的函数.什么叫符合该函数指

27、理解js的继承机制(转载自阮一峰)

Javascript继承机制的设计思想 作者: 阮一峰 日期: 2011年6月 5日 我一直很难理解Javascript语言的继承机制. 它没有"子类"和"父类"的概念,也没有"类"(class)和"实例"(instance)的区分,全靠一种很奇特的"原型链"(prototype chain)模式,来实现继承. 我花了很多时间,学习这个部分,还做了很多笔记.但是都属于强行记忆,无法从根本上理解. 直到昨天,

理解JS的事件委託

首先我們來看一下下面的例子: 假設我現在有一個button列表,當我點擊他們其中的某一個時,我將會對他們標記一個"active"類,當我再點擊一次,則刪除這個類,下面是html代碼: <ul class="toolbar"> <li><button class="btn">Pencil</button></li> <li><button class="btn&

node.js之事件机制

EventEmitter类 方法名与参数 描述 参数说明 addListener(event,listener) 对指定的事件绑定事件处理函数 参数一是事件名称,参数二是事件处理函数 on(event,listener) 对指定的事件绑定事件处理函数(addlistener别名)   once(event,listener) 对指定事件指定只执行一次的时间处理函数   removeListener(event,listener) 对指定事件解除事件处理函数   removeAllListener

JavaScript 详说事件机制之冒泡、捕获、传播、委托

DOM事件流(event  flow )存在三个阶段:事件捕获阶段.处于目标阶段.事件冒泡阶段. 事件捕获(dubbed  bubbling):通俗的理解就是,当鼠标点击或者触发dom事件时,浏览器会从根节点开始由外到内进行事件传播,即点击了子元素,如果父元素通过事件捕获方式注册了对应的事件的话,会先触发父元素绑定的事件. 事件冒泡(event  capturing):与事件捕获恰恰相反,事件冒泡顺序是由内到外进行事件传播,直到根节点. 无论是事件捕获还是事件冒泡,它们都有一个共同的行为,就是事